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));
        }
示例#2
0
        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));
                }
            }
        }