public static async Task <HttpResponseMessage> SetBeneficialOwnerRun( [HttpTrigger(AuthorizationLevel.Function, "POST", Route = "SetBeneficialOwner/{accountnumber}/{ownername}")] HttpRequestMessage req, string accountnumber, string ownername, [EventStream("Bank", "Account", "{accountnumber}")] EventStream bankAccountEvents) { // Set the start time for how long it took to process the message DateTime startTime = DateTime.UtcNow; if (await bankAccountEvents.Exists()) { if (!string.IsNullOrEmpty(ownername)) { Account.Events.BeneficiarySet evtBeneficiary = new Account.Events.BeneficiarySet() { BeneficiaryName = ownername }; await bankAccountEvents.AppendEvent(evtBeneficiary); } return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.OK, FunctionResponse.CreateResponse(startTime, false, $"Beneficial owner of account {accountnumber} set"), FunctionResponse.MEDIA_TYPE)); } else { return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.OK, FunctionResponse.CreateResponse(startTime, true, $"Account {accountnumber} does not exist"), FunctionResponse.MEDIA_TYPE)); } }
public async Task CreateNew_TestMethod() { bool expected = true; bool actual = false; EventStream testObj = new EventStream(new EventStreamAttribute("Bank", "Account", Guid.NewGuid().ToString())); MockEventOne testEvent = new MockEventOne() { EventTypeName = "Another test Event Happened" }; await testObj.AppendEvent(testEvent); actual = await testObj.Exists(); Assert.AreEqual(expected, actual); }
public async Task Append_Event_TestMethod() { bool expected = true; bool actual = false; EventStream testObj = new EventStream(new EventStreamAttribute("Bank", "Account", "Instance 1234")); MockEventOne testEvent = new MockEventOne() { EventTypeName = "Test Event Happened" }; await testObj.AppendEvent(testEvent); actual = await testObj.Exists(); Assert.AreEqual(expected, actual); }
public static async Task <HttpResponseMessage> DepositMoneyRun( [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"DepositMoney/{accountnumber}")] HttpRequestMessage req, string accountnumber, [EventStream("Bank", "Account", "{accountnumber}")] EventStream bankAccountEvents) { // Set the start time for how long it took to process the message DateTime startTime = DateTime.UtcNow; if (!await bankAccountEvents.Exists()) { return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.NotFound, FunctionResponse.CreateResponse(startTime, true, $"Account {accountnumber} does not exist"), FunctionResponse.MEDIA_TYPE)); } else { // get the request body... MoneyDepositData data = await req.Content.ReadAsAsync <MoneyDepositData>(); // create a deposited event DateTime dateDeposited = DateTime.UtcNow; Account.Events.MoneyDeposited evDeposited = new Account.Events.MoneyDeposited() { LoggedDepositDate = dateDeposited, AmountDeposited = data.DepositAmount, Commentary = data.Commentary, Source = data.Source }; await bankAccountEvents.AppendEvent(evDeposited); return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.OK, FunctionResponse.CreateResponse(startTime, false, $"{data.DepositAmount} deposited to account {accountnumber} "), FunctionResponse.MEDIA_TYPE)); } }
public static async Task <Tuple <string, bool> > AccrueInterestForSpecificAccount ([ActivityTrigger] IDurableActivityContext accrueInterestContext) { const decimal DEBIT_INTEREST_RATE = 0.001M; const decimal CREDIT_INTEREST_RATE = 0.0005M; string accountNumber = accrueInterestContext.GetInput <string>(); #region Tracing telemetry Activity.Current.AddTag("Account Number", accountNumber); #endregion if (!string.IsNullOrEmpty(accountNumber)) { EventStream bankAccountEvents = new EventStream(new EventStreamAttribute("Bank", "Account", accountNumber)); if (await bankAccountEvents.Exists()) { // Has the accrual been done today for this account? Classification clsAccruedToday = new Classification(new ClassificationAttribute("Bank", "Account", accountNumber, nameof(InterestAccruedToday))); ClassificationResponse isAccrued = await clsAccruedToday.Classify <InterestAccruedToday>(); if (isAccrued.Result != ClassificationResponse.ClassificationResults.Include) { // Get the account balance Projection prjBankAccountBalance = new Projection(new ProjectionAttribute("Bank", "Account", accountNumber, nameof(Balance))); // Get the current account balance, as at midnight Balance projectedBalance = await prjBankAccountBalance.Process <Balance>(DateTime.Today); if (null != projectedBalance) { Account.Events.InterestAccrued evAccrued = new Account.Events.InterestAccrued() { Commentary = $"Daily scheduled interest accrual", AccrualEffectiveDate = DateTime.Today // set the accrual to midnight today }; // calculate the accrual amount if (projectedBalance.CurrentBalance >= 0) { // Using the credit rate evAccrued.AmountAccrued = CREDIT_INTEREST_RATE * projectedBalance.CurrentBalance; evAccrued.InterestRateInEffect = CREDIT_INTEREST_RATE; } else { // Use the debit rate evAccrued.AmountAccrued = DEBIT_INTEREST_RATE * projectedBalance.CurrentBalance; evAccrued.InterestRateInEffect = DEBIT_INTEREST_RATE; } try { await bankAccountEvents.AppendEvent(evAccrued, isAccrued.AsOfSequence); } catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException) { // We can't be sure this hasn't already run... return(new Tuple <string, bool>(accountNumber, false)); } } } } } return(new Tuple <string, bool>(accountNumber, true)); }
/// <summary> /// Perform the underlying validation on the specified command /// </summary> /// <param name="commandId"> /// The unique identifier of the command to validate /// </param> private static async Task <bool> ValidateCreateLeagueCommand(string commandId, ILogger log, IWriteContext writeContext = null) { const string COMMAND_NAME = @"create-league"; Guid commandGuid; // use custom assembly resolve handler using (new AzureFunctionsResolveAssembly()) { if (Guid.TryParse(commandId, out commandGuid)) { #region Logging if (null != log) { log.LogDebug($"Validating command {commandId} in ValidateCreateLeagueCommand"); } #endregion // Get the current state of the command... Projection getCommandState = new Projection(Constants.Domain_Command, COMMAND_NAME, commandGuid.ToString(), nameof(Command_Summary_Projection)); if (null != getCommandState) { #region Logging if (null != log) { log.LogDebug($"Projection processor created in ValidateCreateLeagueCommand"); } #endregion Command_Summary_Projection cmdProjection = new Command_Summary_Projection(log); await getCommandState.Process(cmdProjection); if ((cmdProjection.CurrentSequenceNumber > 0) || (cmdProjection.ProjectionValuesChanged())) { #region Logging if (null != log) { log.LogDebug($"Command { cmdProjection.CommandName} projection run for {commandGuid} in ValidateCreateLeagueCommand"); } #endregion if (cmdProjection.CurrentState == Command_Summary_Projection.CommandState.Completed) { // No need to validate a completed command #region Logging if (null != log) { log.LogWarning($"Command {commandId} is complete so no need to validate in ValidateCreateLeagueCommand"); } #endregion return(true); } if (cmdProjection.CurrentState == Command_Summary_Projection.CommandState.Validated) { // No need to process a completed projection #region Logging if (null != log) { log.LogWarning($"Command {commandId} is validated so no need to validate again in ValidateCreateLeagueCommand"); } #endregion return(true); } if ((cmdProjection.CurrentState == Command_Summary_Projection.CommandState.Created) || (cmdProjection.CurrentState == Command_Summary_Projection.CommandState.Invalid)) { bool leagueNameValid = false; bool incoporatedDateValid = false; // New or previously invalid command can be validated if (cmdProjection.ParameterIsSet(nameof(Create_New_League_Definition.LeagueName))) { // League name may not be blank string leagueName = cmdProjection.GetParameter <string>(nameof(Create_New_League_Definition.LeagueName)); if (string.IsNullOrWhiteSpace(leagueName)) { await CommandErrorLogRecord.LogCommandValidationError(commandGuid, COMMAND_NAME, true, "League name may not be blank"); #region Logging if (null != log) { log.LogWarning($"Command {COMMAND_NAME } :: {commandId} has a blank league name in ValidateCreateLeagueCommand"); } #endregion } else { leagueNameValid = true; // was the name already used? EventStream leagueEvents = new EventStream(@"Leagues", "League", leagueName); if (null != leagueEvents) { bool alreadyUsed = await leagueEvents.Exists(); if (alreadyUsed) { await CommandErrorLogRecord.LogCommandValidationError(commandGuid, COMMAND_NAME, true, $"League name '{leagueName}' already created."); #region Logging if (null != log) { log.LogWarning($"Command {COMMAND_NAME } :: {commandId} league name '{leagueName}' already created in ValidateCreateLeagueCommand"); } #endregion leagueNameValid = false; } } } } // The incoporation date may not be in the future if (cmdProjection.ParameterIsSet(nameof(ICreate_New_League_Definition.Date_Incorporated))) { DateTime dateIncorporated = cmdProjection.GetParameter <DateTime>(nameof(ICreate_New_League_Definition.Date_Incorporated)); if (dateIncorporated > DateTime.UtcNow) { await CommandErrorLogRecord.LogCommandValidationError(commandGuid, COMMAND_NAME, false, "Incorporation date is in the future"); #region Logging if (null != log) { log.LogWarning($"Command {COMMAND_NAME } :: {commandId} has a future dated incorporation date in ValidateCreateLeagueCommand"); } #endregion } else { incoporatedDateValid = true; } } if (incoporatedDateValid && leagueNameValid) { await CommandErrorLogRecord.LogCommandValidationSuccess(commandGuid, COMMAND_NAME); } return(incoporatedDateValid && leagueNameValid); } } else { // No events were read into the projection so do nothing #region Logging if (null != log) { log.LogWarning($"No command events read for {commandId} in ValidateCreateLeagueCommand"); } #endregion } } } return(false); } }
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> AccrueInterestRun( [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"AccrueInterest/{accountnumber}")] HttpRequestMessage req, string accountnumber, [EventStream("Bank", "Account", "{accountnumber}")] EventStream bankAccountEvents, [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance, [Classification("Bank", "Account", "{accountnumber}", nameof(InterestAccruedToday))] Classification clsAccruedToday) { // Set the start time for how long it took to process the message DateTime startTime = DateTime.UtcNow; if (!await bankAccountEvents.Exists()) { // You cannot accrue 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))); } ClassificationResponse isAccrued = await clsAccruedToday.Classify <InterestAccruedToday>(); if (isAccrued.Result == ClassificationResponse.ClassificationResults.Include) { // The accrual for today has been performed for this account return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Interest accrual already done on account {accountnumber} today", isAccrued.AsOfSequence), FunctionResponse.MEDIA_TYPE )); } // get the request body... InterestAccrualData data = await req.Content.ReadAsAsync <InterestAccrualData>(); // Get the current account balance, as at midnight Balance projectedBalance = await prjBankAccountBalance.Process <Balance>(DateTime.Today); if (null != projectedBalance) { Account.Events.InterestAccrued evAccrued = new Account.Events.InterestAccrued() { Commentary = data.Commentary, AccrualEffectiveDate = DateTime.Today // set the accrual to midnight today }; if (projectedBalance.CurrentBalance >= 0) { // Using the credit rate evAccrued.AmountAccrued = data.CreditInterestRate * projectedBalance.CurrentBalance; } else { // Use the debit rate evAccrued.AmountAccrued = data.DebitInterestRate * projectedBalance.CurrentBalance; } try { await bankAccountEvents.AppendEvent(evAccrued, isAccrued.AsOfSequence); } catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException exWrite) { return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Failed to write interest accrual event {exWrite.Message}", projectedBalance.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK, ProjectionFunctionResponse.CreateResponse(startTime, false, $"Interest accrued for account {accountnumber} is {evAccrued.AmountAccrued}", 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} for interest accrual", 0), FunctionResponse.MEDIA_TYPE )); } }
public static async Task <HttpResponseMessage> SetOverdraftLimitRun( [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"SetOverdraftLimit/{accountnumber}")] HttpRequestMessage req, string accountnumber, [EventStream("Bank", "Account", "{accountnumber}")] EventStream bankAccountEvents, [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance) { // Set the start time for how long it took to process the message DateTime startTime = DateTime.UtcNow; if (!await bankAccountEvents.Exists()) { // You cannot set an overdraft if the account does not exist return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Account {accountnumber} does not exist", 0))); } else { // get the request body... OverdraftSetData data = await req.Content.ReadAsAsync <OverdraftSetData>(); // get the current account balance Balance projectedBalance = await prjBankAccountBalance.Process <Balance>(); if (null != projectedBalance) { if (projectedBalance.CurrentBalance >= (0 - data.NewOverdraftLimit)) { // attempt to set the new overdraft limit Account.Events.OverdraftLimitSet evOverdraftSet = new Account.Events.OverdraftLimitSet() { OverdraftLimit = data.NewOverdraftLimit, Commentary = data.Commentary }; try { await bankAccountEvents.AppendEvent(evOverdraftSet, projectedBalance.CurrentSequenceNumber); } catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException exWrite) { return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Failed to write overdraft limit event {exWrite.Message}", projectedBalance.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK, ProjectionFunctionResponse.CreateResponse(startTime, false, $"{data.NewOverdraftLimit } set as the new overdraft limit for account {accountnumber}", projectedBalance.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } else { return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Account {accountnumber} has an outstanding balance beyond the new limit {data.NewOverdraftLimit} (Current balance: {projectedBalance.CurrentBalance} )", 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}", 0), FunctionResponse.MEDIA_TYPE )); } } }
public static async Task <HttpResponseMessage> OpenAccountRun( [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"OpenAccount/{accountnumber}")] HttpRequestMessage req, string accountnumber, [EventStream("Bank", "Account", "{accountnumber}")] EventStream bankAccountEvents) { // Set the start time for how long it took to process the message DateTime startTime = DateTime.UtcNow; if (await bankAccountEvents.Exists()) { return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.Forbidden, FunctionResponse.CreateResponse(startTime, true, $"Account {accountnumber} already exists"), FunctionResponse.MEDIA_TYPE)); } else { // Get request body AccountOpeningData data = await req.Content.ReadAsAsync <AccountOpeningData>(); // Append a "created" event DateTime dateCreated = DateTime.UtcNow; Account.Events.Opened evtOpened = new Account.Events.Opened() { LoggedOpeningDate = dateCreated }; if (!string.IsNullOrWhiteSpace(data.Commentary)) { evtOpened.Commentary = data.Commentary; } try { await bankAccountEvents.AppendEvent(evtOpened, streamConstraint : EventStreamExistenceConstraint.MustBeNew ); } catch (EventStreamWriteException exWrite) { return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.Conflict, FunctionResponse.CreateResponse(startTime, true, $"Account {accountnumber} had a conflict error on creation {exWrite.Message }"), FunctionResponse.MEDIA_TYPE)); } // If there is an initial deposit in the account opening data, append a "deposit" event if (data.OpeningBalance.HasValue) { Account.Events.MoneyDeposited evtInitialDeposit = new Account.Events.MoneyDeposited() { AmountDeposited = data.OpeningBalance.Value, LoggedDepositDate = dateCreated, Commentary = "Opening deposit" }; await bankAccountEvents.AppendEvent(evtInitialDeposit); } // If there is a beneficiary in the account opening data append a "beneficiary set" event if (!string.IsNullOrEmpty(data.ClientName)) { Account.Events.BeneficiarySet evtBeneficiary = new Account.Events.BeneficiarySet() { BeneficiaryName = data.ClientName }; await bankAccountEvents.AppendEvent(evtBeneficiary); } return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.Created, FunctionResponse.CreateResponse(startTime, false, $"Account { accountnumber} created"), 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)); } } }