public static async Task <HttpResponseMessage> GetBalanceRun( [HttpTrigger(AuthorizationLevel.Function, "GET", Route = @"GetBalance/{accountnumber}/{asOfDate?}")] HttpRequestMessage req, string accountnumber, string asOfDate, [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; string result = $"No balance found for account {accountnumber}"; if (null != prjBankAccountBalance) { if (await prjBankAccountBalance.Exists()) { // Get request body Nullable <DateTime> asOfDateValue = null; if (!string.IsNullOrEmpty(asOfDate)) { DateTime dtTest; if (DateTime.TryParse(asOfDate, out dtTest)) { asOfDateValue = dtTest; } } // Run the "Balance" projection Balance projectedBalance = await prjBankAccountBalance.Process <Balance>(asOfDateValue); if (null != projectedBalance) { result = $"Balance for account {accountnumber} is ${projectedBalance.CurrentBalance} "; return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK, ProjectionFunctionResponse.CreateResponse(startTime, false, result, projectedBalance.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } } else { result = $"Account {accountnumber} is not yet created - cannot retrieve a balance for it"; return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.NotFound, ProjectionFunctionResponse.CreateResponse(startTime, true, result, 0), FunctionResponse.MEDIA_TYPE)); } } // If we got here no balance was found return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.NotFound, ProjectionFunctionResponse.CreateResponse(startTime, true, result, 0), FunctionResponse.MEDIA_TYPE)); }
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> 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> 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)); } } }