Service/Data access class for Rock.Model.FinancialScheduledTransaction entity objects.
        protected void bbtnDelete_Click( object sender, EventArgs e )
        {
            BootstrapButton bbtnDelete = (BootstrapButton)sender;
            RepeaterItem riItem = (RepeaterItem)bbtnDelete.NamingContainer;

            HiddenField hfScheduledTransactionId = (HiddenField)riItem.FindControl( "hfScheduledTransactionId" );
            Literal content = (Literal)riItem.FindControl( "lLiquidContent" );
            Button btnEdit = (Button)riItem.FindControl( "btnEdit" );

            var rockContext = new Rock.Data.RockContext();
            FinancialScheduledTransactionService fstService = new FinancialScheduledTransactionService( rockContext );
            var currentTransaction = fstService.Get( Int32.Parse(hfScheduledTransactionId.Value) );

            string errorMessage = string.Empty;
            if ( fstService.Cancel( currentTransaction, out errorMessage ) )
            {
                rockContext.SaveChanges();
                content.Text = String.Format( "<div class='alert alert-success'>Your recurring {0} has been deleted.</div>", GetAttributeValue( "TransactionLabel" ).ToLower() );
            }
            else
            {
                content.Text = String.Format( "<div class='alert alert-danger'>An error occured while deleting your scheduled transation. Message: {0}</div>", errorMessage );
            }

            bbtnDelete.Visible = false;
            btnEdit.Visible = false;
        }
 private void DeleteOldTransaction( int scheduledTransactionId )
 {
     using ( var rockContext = new Rock.Data.RockContext() )
     {
         FinancialScheduledTransactionService fstService = new FinancialScheduledTransactionService( rockContext );
         var currentTransaction = fstService.Get( scheduledTransactionId );
         if ( currentTransaction != null && currentTransaction.FinancialGateway != null )
         {
             currentTransaction.FinancialGateway.LoadAttributes( rockContext );
         }
         string errorMessage = string.Empty;
         if ( fstService.Cancel( currentTransaction, out errorMessage ) )
         {
             try
             {
                 fstService.GetStatus( currentTransaction, out errorMessage );
             }
             catch { }
             rockContext.SaveChanges();
             //content.Text = String.Format( "<div class='alert alert-success'>Your recurring {0} has been deleted.</div>", GetAttributeValue( "TransactionLabel" ).ToLower() );
         }
         else
         {
             //content.Text = String.Format( "<div class='alert alert-danger'>An error occured while deleting your scheduled transation. Message: {0}</div>", errorMessage );
         }
     }
 }
        private void SaveScheduledTransaction( FinancialGateway financialGateway, GatewayComponent gateway, Person person, PaymentInfo paymentInfo, PaymentSchedule schedule, FinancialScheduledTransaction scheduledTransaction, RockContext rockContext )
        {
            scheduledTransaction.TransactionFrequencyValueId = schedule.TransactionFrequencyValue.Id;
            scheduledTransaction.StartDate = schedule.StartDate;
            scheduledTransaction.AuthorizedPersonAliasId = person.PrimaryAliasId.Value;
            scheduledTransaction.FinancialGatewayId = financialGateway.Id;

            if ( scheduledTransaction.FinancialPaymentDetail == null )
            {
                scheduledTransaction.FinancialPaymentDetail = new FinancialPaymentDetail();
            }
            scheduledTransaction.FinancialPaymentDetail.SetFromPaymentInfo( paymentInfo, gateway, rockContext );

            Guid sourceGuid = Guid.Empty;
            if ( Guid.TryParse( GetAttributeValue( "Source" ), out sourceGuid ) )
            {
                var source = DefinedValueCache.Read( sourceGuid );
                if ( source != null )
                {
                    scheduledTransaction.SourceTypeValueId = source.Id;
                }
            }

            var changeSummary = new StringBuilder();
            changeSummary.AppendFormat( "{0} starting {1}", schedule.TransactionFrequencyValue.Value, schedule.StartDate.ToShortDateString() );
            changeSummary.AppendLine();
            changeSummary.Append( paymentInfo.CurrencyTypeValue.Value );
            if ( paymentInfo.CreditCardTypeValue != null )
            {
                changeSummary.AppendFormat( " - {0}", paymentInfo.CreditCardTypeValue.Value );
            }
            changeSummary.AppendFormat( " {0}", paymentInfo.MaskedNumber );
            changeSummary.AppendLine();

            foreach ( var account in SelectedAccounts.Where( a => a.Amount > 0 ) )
            {
                var transactionDetail = new FinancialScheduledTransactionDetail();
                transactionDetail.Amount = account.Amount;
                transactionDetail.AccountId = account.Id;
                scheduledTransaction.ScheduledTransactionDetails.Add( transactionDetail );
                changeSummary.AppendFormat( "{0}: {1}", account.Name, account.Amount.FormatAsCurrency() );
                changeSummary.AppendLine();
            }

            if ( !string.IsNullOrWhiteSpace( paymentInfo.Comment1 ) )
            {
                changeSummary.Append( paymentInfo.Comment1 );
                changeSummary.AppendLine();
            }

            var transactionService = new FinancialScheduledTransactionService( rockContext );
            transactionService.Add( scheduledTransaction );
            rockContext.SaveChanges();

            // If this is a transfer, now we can delete the old transaction
            if ( _scheduledTransactionToBeTransferred != null )
            {
                DeleteOldTransaction( _scheduledTransactionToBeTransferred.Id );
            }

            // Add a note about the change
            var noteType = NoteTypeCache.Read( Rock.SystemGuid.NoteType.SCHEDULED_TRANSACTION_NOTE.AsGuid() );
            if ( noteType != null )
            {
                var noteService = new NoteService( rockContext );
                var note = new Note();
                note.NoteTypeId = noteType.Id;
                note.EntityId = scheduledTransaction.Id;
                note.Caption = "Created Transaction";
                note.Text = changeSummary.ToString();
                noteService.Add( note );
            }
            rockContext.SaveChanges();

            ScheduleId = scheduledTransaction.GatewayScheduleId;
            TransactionCode = scheduledTransaction.TransactionCode;
        }
        /// <summary>
        /// Fetches the old (to be transferred) scheduled transaction and verifies
        /// that the target person is the same on the scheduled transaction.  Then
        /// it puts it into the _scheduledTransactionToBeTransferred private field 
        /// for use throughout the entry process so that its values can be used on
        /// the form for the new transaction.
        /// </summary>
        /// <param name="scheduledTransactionId">The scheduled transaction identifier.</param>
        private void InitializeTransfer( int? scheduledTransactionId )
        {
            if ( scheduledTransactionId == null )
            {
                return;
            }

            RockContext rockContext = new RockContext();
            var scheduledTransaction = new FinancialScheduledTransactionService( rockContext ).Get( scheduledTransactionId.Value );
            var personService = new PersonService( rockContext );

            // get business giving id
            var givingIds = personService.GetBusinesses( _targetPerson.Id ).Select( g => g.GivingId ).ToList();

            // add the person's regular giving id
            givingIds.Add( _targetPerson.GivingId );

            // Make sure the current person is the authorized person, otherwise return
            if ( scheduledTransaction == null || ! givingIds.Contains( scheduledTransaction.AuthorizedPersonAlias.Person.GivingId ) )
            {
                return;
            }

            _scheduledTransactionToBeTransferred = scheduledTransaction;

            // Set the frequency to be the same on the initial page build
            if ( !IsPostBack )
            {
                btnFrequency.SelectedValue = scheduledTransaction.TransactionFrequencyValueId.ToString();
                dtpStartDate.SelectedDate = ( scheduledTransaction.NextPaymentDate.HasValue ) ? scheduledTransaction.NextPaymentDate : RockDateTime.Today.AddDays( 1 );
            }
        }
        /// <summary>
        /// Processes the payment information.
        /// </summary>
        /// <param name="errorMessage">The error message.</param>
        /// <returns></returns>
        private bool ProcessPaymentInfo( out string errorMessage )
        {
            errorMessage = string.Empty;

            var errorMessages = new List<string>();

            // Validate that an amount was entered
            if ( SelectedAccounts.Sum( a => a.Amount ) <= 0 )
            {
                errorMessages.Add( "Make sure you've entered an amount for at least one account" );
            }

            // Validate that no negative amounts were entered
            if ( SelectedAccounts.Any( a => a.Amount < 0 ) )
            {
                errorMessages.Add( "Make sure the amount you've entered for each account is a positive amount" );
            }

            string howOften = DefinedValueCache.Read( btnFrequency.SelectedValueAsId().Value ).Name;
            DateTime when = DateTime.MinValue;

            // Make sure a repeating payment starts in the future
            if ( dtpStartDate.SelectedDate.HasValue && dtpStartDate.SelectedDate > DateTime.Today )
            {
                when = dtpStartDate.SelectedDate.Value;
            }
            else
            {
                errorMessages.Add( "Make sure the Next  Gift date is in the future (after today)" );
            }

            if ( hfPaymentTab.Value == "ACH" )
            {
                // Validate ach options
                if ( rblSavedAch.Items.Count > 0 && ( rblSavedAch.SelectedValueAsInt() ?? 0 ) > 0 )
                {
                    // TODO: Find saved account
                }
                else
                {
                    if ( string.IsNullOrWhiteSpace( txtBankName.Text ) )
                    {
                        errorMessages.Add( "Make sure to enter a bank name" );
                    }

                    if ( string.IsNullOrWhiteSpace( txtRoutingNumber.Text ) )
                    {
                        errorMessages.Add( "Make sure to enter a valid routing number" );
                    }

                    if ( string.IsNullOrWhiteSpace( txtAccountNumber.Text ) )
                    {
                        errorMessages.Add( "Make sure to enter a valid account number" );
                    }
                }
            }
            else if ( hfPaymentTab.Value == "CreditCard" )
            {
                // validate cc options
                if ( rblSavedCC.Items.Count > 0 && ( rblSavedCC.SelectedValueAsInt() ?? 0 ) > 0 )
                {
                    // TODO: Find saved card
                }
                else
                {
                    if ( Gateway.SplitNameOnCard )
                    {
                        if ( string.IsNullOrWhiteSpace( txtCardFirstName.Text ) || string.IsNullOrWhiteSpace( txtCardLastName.Text ) )
                        {
                            errorMessages.Add( "Make sure to enter a valid first and last name as it appears on your credit card" );
                        }
                    }
                    else
                    {
                        if ( string.IsNullOrWhiteSpace( txtCardName.Text ) )
                        {
                            errorMessages.Add( "Make sure to enter a valid name as it appears on your credit card" );
                        }
                    }

                    if ( string.IsNullOrWhiteSpace( txtCreditCard.Text ) )
                    {
                        errorMessages.Add( "Make sure to enter a valid credit card number" );
                    }

                    var currentMonth = DateTime.Today;
                    currentMonth = new DateTime( currentMonth.Year, currentMonth.Month, 1 );
                    if ( !mypExpiration.SelectedDate.HasValue || mypExpiration.SelectedDate.Value.CompareTo( currentMonth ) < 0 )
                    {
                        errorMessages.Add( "Make sure to enter a valid credit card expiration date" );
                    }

                    if ( string.IsNullOrWhiteSpace( txtCVV.Text ) )
                    {
                        errorMessages.Add( "Make sure to enter a valid credit card security code" );
                    }
                }
            }

            if ( errorMessages.Any() )
            {
                errorMessage = errorMessages.AsDelimited( "<br/>" );
                return false;
            }

            using ( new UnitOfWorkScope() )
            {
                FinancialScheduledTransaction scheduledTransaction = null;

                if ( ScheduledTransactionId.HasValue )
                {
                    scheduledTransaction = new  FinancialScheduledTransactionService().Get( ScheduledTransactionId.Value );
                }

                if ( scheduledTransaction == null )
                {
                    errorMessage = "There was a problem getting the transaction information";
                    return false;
                }

                if ( scheduledTransaction.AuthorizedPerson == null )
                {
                    errorMessage = "There was a problem determining the person associated with the transaction";
                    return false;
                }

                PaymentInfo paymentInfo = GetPaymentInfo( new PersonService(), scheduledTransaction );
                if ( paymentInfo != null )
                {
                    tdName.Description = paymentInfo.FullName;
                    tdTotal.Description = paymentInfo.Amount.ToString( "C" );

                    if (paymentInfo.CurrencyTypeValue != null)
                    {
                        tdPaymentMethod.Description = paymentInfo.CurrencyTypeValue.Description;
                        tdPaymentMethod.Visible = true;
                    }
                    else
                    {
                        tdPaymentMethod.Visible = false;
                    }

                    if (string.IsNullOrWhiteSpace(paymentInfo.MaskedNumber))
                    {
                        tdAccountNumber.Visible = false;
                    }
                    else
                    {
                        tdAccountNumber.Visible = true;
                        tdAccountNumber.Description = paymentInfo.MaskedNumber;
                    }

                    tdWhen.Description = string.Format( "{0} starting on {1}", howOften, when.ToShortDateString());
                }
            }

            rptAccountListConfirmation.DataSource = SelectedAccounts.Where( a => a.Amount != 0 );
            rptAccountListConfirmation.DataBind();

            string nextDate = dtpStartDate.SelectedDate.HasValue ? dtpStartDate.SelectedDate.Value.ToShortDateString() : "?";
            string frequency = DefinedValueCache.Read( btnFrequency.SelectedValueAsInt() ?? 0 ).Description;
            tdWhen.Description = frequency + " starting on " + nextDate;

            return true;
        }
        /// <summary>
        /// Handles the RowSelected event of the rGridGivingProfile control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RowEventArgs"/> instance containing the event data.</param>
        protected void rGridGivingProfile_Edit( object sender, RowEventArgs e )
        {
            string urlEncodedKey = string.Empty;
            if ( TargetPerson != null )
            {
                urlEncodedKey = TargetPerson.UrlEncodedKey;
            }
            else
            {
                var txn = new FinancialScheduledTransactionService( new RockContext() ).Get( (int)e.RowKeyValue );
                if ( txn != null && txn.AuthorizedPerson != null )
                {
                    urlEncodedKey = txn.AuthorizedPerson.UrlEncodedKey;
                }
            }

            var parms = new Dictionary<string, string>();
            parms.Add( "Txn", rGridGivingProfile.DataKeys[e.RowIndex]["id"].ToString() );
            if ( !string.IsNullOrWhiteSpace( urlEncodedKey ) )
            {
                parms.Add( "Person", urlEncodedKey );
            }

            NavigateToLinkedPage( "EditPage", parms );
        }
        /// <summary>
        /// Processes the confirmation.
        /// </summary>
        /// <param name="errorMessage">The error message.</param>
        /// <returns></returns>
        private bool ProcessConfirmation( out string errorMessage )
        {
            var rockContext = new RockContext();
            if ( string.IsNullOrWhiteSpace( TransactionCode ) )
            {
                GatewayComponent gateway = hfPaymentTab.Value == "ACH" ? _achGateway : _ccGateway;
                if ( gateway == null )
                {
                    errorMessage = "There was a problem creating the payment gateway information";
                    return false;
                }

                Person person = GetPerson( true );
                if ( person == null )
                {
                    errorMessage = "There was a problem creating the person information";
                    return false;
                }

                if ( !person.PrimaryAliasId.HasValue)
                {
                    errorMessage = "There was a problem creating the person's primary alias";
                    return false;
                }

                PaymentInfo paymentInfo = GetPaymentInfo();
                if ( paymentInfo == null )
                {
                    errorMessage = "There was a problem creating the payment information";
                    return false;
                }
                else
                {
                    paymentInfo.FirstName = person.FirstName;
                    paymentInfo.LastName = person.LastName;
                }

                if ( paymentInfo.CreditCardTypeValue != null )
                {
                    CreditCardTypeValueId = paymentInfo.CreditCardTypeValue.Id;
                }

                PaymentSchedule schedule = GetSchedule();
                if ( schedule != null )
                {
                    schedule.PersonId = person.Id;

                    var scheduledTransaction = gateway.AddScheduledPayment( schedule, paymentInfo, out errorMessage );
                    if ( scheduledTransaction != null  )
                    {
                        scheduledTransaction.TransactionFrequencyValueId = schedule.TransactionFrequencyValue.Id;
                        scheduledTransaction.AuthorizedPersonAliasId = person.PrimaryAliasId.Value;
                        scheduledTransaction.GatewayEntityTypeId = EntityTypeCache.Read( gateway.TypeGuid ).Id;
                        scheduledTransaction.CurrencyTypeValueId = paymentInfo.CurrencyTypeValue.Id;
                        scheduledTransaction.CreditCardTypeValueId = CreditCardTypeValueId;

                        var changeSummary = new StringBuilder();
                        changeSummary.AppendFormat( "{0} starting {1}", schedule.TransactionFrequencyValue.Value, schedule.StartDate.ToShortDateString() );
                        changeSummary.AppendLine();
                        changeSummary.Append( paymentInfo.CurrencyTypeValue.Value );
                        if (paymentInfo.CreditCardTypeValue != null)
                        {
                            changeSummary.AppendFormat( " - {0}", paymentInfo.CreditCardTypeValue.Value );
                        }
                        changeSummary.AppendFormat( " {0}", paymentInfo.MaskedNumber );
                        changeSummary.AppendLine();

                        foreach ( var account in SelectedAccounts.Where( a => a.Amount > 0 ) )
                        {
                            var transactionDetail = new FinancialScheduledTransactionDetail();
                            transactionDetail.Amount = account.Amount;
                            transactionDetail.AccountId = account.Id;
                            scheduledTransaction.ScheduledTransactionDetails.Add( transactionDetail );
                            changeSummary.AppendFormat( "{0}: {1:C2}", account.Name, account.Amount );
                            changeSummary.AppendLine();
                        }

                        var transactionService = new FinancialScheduledTransactionService( rockContext );
                        transactionService.Add( scheduledTransaction );
                        rockContext.SaveChanges();

                        // Add a note about the change
                        var noteTypeService = new NoteTypeService( rockContext );
                        var noteType = noteTypeService.Get( scheduledTransaction.TypeId, "Note" );

                        var noteService = new NoteService( rockContext );
                        var note = new Note();
                        note.NoteTypeId = noteType.Id;
                        note.EntityId = scheduledTransaction.Id;
                        note.Caption = "Created Transaction";
                        note.Text = changeSummary.ToString();
                        noteService.Add( note );

                        rockContext.SaveChanges();

                        ScheduleId = scheduledTransaction.GatewayScheduleId;
                        TransactionCode = scheduledTransaction.TransactionCode;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    var transaction = gateway.Charge( paymentInfo, out errorMessage );
                    if ( transaction != null )
                    {
                        transaction.TransactionDateTime = RockDateTime.Now;
                        transaction.AuthorizedPersonAliasId = person.PrimaryAliasId;
                        transaction.GatewayEntityTypeId = gateway.TypeId;
                        transaction.TransactionTypeValueId = DefinedValueCache.Read( new Guid( Rock.SystemGuid.DefinedValue.TRANSACTION_TYPE_CONTRIBUTION ) ).Id;
                        transaction.CurrencyTypeValueId = paymentInfo.CurrencyTypeValue.Id;
                        transaction.CreditCardTypeValueId = CreditCardTypeValueId;

                        Guid sourceGuid = Guid.Empty;
                        if ( Guid.TryParse( GetAttributeValue( "Source" ), out sourceGuid ) )
                        {
                            transaction.SourceTypeValueId = DefinedValueCache.Read( sourceGuid ).Id;
                        }

                        foreach ( var account in SelectedAccounts.Where( a => a.Amount > 0 ) )
                        {
                            var transactionDetail = new FinancialTransactionDetail();
                            transactionDetail.Amount = account.Amount;
                            transactionDetail.AccountId = account.Id;
                            transaction.TransactionDetails.Add( transactionDetail );
                        }

                        var batchService = new FinancialBatchService( rockContext );

                        // Get the batch
                        var batch = batchService.Get(
                            GetAttributeValue( "BatchNamePrefix" ),
                            paymentInfo.CurrencyTypeValue,
                            paymentInfo.CreditCardTypeValue,
                            transaction.TransactionDateTime.Value,
                            gateway.BatchTimeOffset );

                        batch.ControlAmount += transaction.TotalAmount;

                        transaction.BatchId = batch.Id;
                        batch.Transactions.Add( transaction );
                        rockContext.SaveChanges();

                        TransactionCode = transaction.TransactionCode;
                    }
                    else
                    {
                        return false;
                    }
                }

                tdTransactionCodeReceipt.Description = TransactionCode;
                tdTransactionCodeReceipt.Visible = !string.IsNullOrWhiteSpace( TransactionCode );

                tdScheduleId.Description = ScheduleId;
                tdScheduleId.Visible = !string.IsNullOrWhiteSpace( ScheduleId );

                tdNameReceipt.Description = paymentInfo.FullName;
                tdPhoneReceipt.Description = paymentInfo.Phone;
                tdEmailReceipt.Description = paymentInfo.Email;
                tdAddressReceipt.Description = string.Format( "{0} {1}, {2} {3}", paymentInfo.Street1, paymentInfo.City, paymentInfo.State, paymentInfo.PostalCode );

                rptAccountListReceipt.DataSource = SelectedAccounts.Where( a => a.Amount != 0 );
                rptAccountListReceipt.DataBind();

                tdTotalReceipt.Description = paymentInfo.Amount.ToString( "C" );

                tdPaymentMethodReceipt.Description = paymentInfo.CurrencyTypeValue.Description;
                tdAccountNumberReceipt.Description = paymentInfo.MaskedNumber;
                tdWhenReceipt.Description = schedule != null ? schedule.ToString() : "Today";

                // If there was a transaction code returned and this was not already created from a previous saved account,
                // show the option to save the account.
                if ( !( paymentInfo is ReferencePaymentInfo ) && !string.IsNullOrWhiteSpace( TransactionCode ) )
                {
                    cbSaveAccount.Visible = true;
                    pnlSaveAccount.Visible = true;
                    txtSaveAccount.Visible = true;

                    // If current person does not have a login, have them create a username and password
                    phCreateLogin.Visible = !new UserLoginService( rockContext ).GetByPersonId( person.Id ).Any();
                }
                else
                {
                    pnlSaveAccount.Visible = false;
                }

                return true;
            }
            else
            {
                pnlDupWarning.Visible = true;
                divActions.Visible = false;
                errorMessage = string.Empty;
                return false;
            }
        }
        /// <summary>
        /// Processes the payments.
        /// </summary>
        /// <param name="gateway">The gateway.</param>
        /// <param name="batchNamePrefix">The batch name prefix.</param>
        /// <param name="payments">The payments.</param>
        /// <param name="batchUrlFormat">The batch URL format.</param>
        /// <returns></returns>
        public static string ProcessPayments( FinancialGateway gateway, string batchNamePrefix, List<Payment> payments, string batchUrlFormat = "" )
        {
            int totalPayments = 0;
            int totalAlreadyDownloaded = 0;
            int totalNoScheduledTransaction = 0;
            int totalAdded = 0;

            var batches = new List<FinancialBatch>();
            var batchSummary = new Dictionary<Guid, List<Payment>>();
            var initialControlAmounts = new Dictionary<Guid, decimal>();

            var allBatchChanges = new Dictionary<Guid, List<string>>();
            var allTxnChanges = new Dictionary<Guid, List<string>>();
            var txnPersonNames = new Dictionary<Guid, string>();

            using ( var rockContext = new RockContext() )
            {
                var accountService = new FinancialAccountService( rockContext );
                var txnService = new FinancialTransactionService( rockContext );
                var batchService = new FinancialBatchService( rockContext );
                var scheduledTxnService = new FinancialScheduledTransactionService( rockContext );

                var contributionTxnType = DefinedValueCache.Read( Rock.SystemGuid.DefinedValue.TRANSACTION_TYPE_CONTRIBUTION.AsGuid() );

                var defaultAccount = accountService.Queryable()
                    .Where( a =>
                        a.IsActive &&
                        !a.ParentAccountId.HasValue &&
                        ( !a.StartDate.HasValue || a.StartDate.Value <= RockDateTime.Now ) &&
                        ( !a.EndDate.HasValue || a.EndDate.Value >= RockDateTime.Now )
                        )
                    .OrderBy( a => a.Order )
                    .FirstOrDefault();

                var batchTxnChanges = new Dictionary<Guid, List<string>>();
                var batchBatchChanges = new Dictionary<Guid, List<string>>();

                foreach ( var payment in payments.Where( p => p.Amount > 0.0M ) )
                {
                    totalPayments++;

                    // Only consider transactions that have not already been added
                    if ( txnService.GetByTransactionCode( payment.TransactionCode ) == null )
                    {
                        var scheduledTransaction = scheduledTxnService.GetByScheduleId( payment.GatewayScheduleId );
                        if ( scheduledTransaction != null )
                        {
                            scheduledTransaction.IsActive = payment.ScheduleActive;

                            var txnChanges = new List<string>();

                            var transaction = new FinancialTransaction();
                            transaction.FinancialPaymentDetail = new FinancialPaymentDetail();

                            transaction.Guid = Guid.NewGuid();
                            allTxnChanges.Add( transaction.Guid, txnChanges );
                            txnChanges.Add( "Created Transaction (Downloaded from Gateway)" );

                            transaction.TransactionCode = payment.TransactionCode;
                            History.EvaluateChange( txnChanges, "Transaction Code", string.Empty, transaction.TransactionCode );

                            transaction.TransactionDateTime = payment.TransactionDateTime;
                            History.EvaluateChange( txnChanges, "Date/Time", null, transaction.TransactionDateTime );

                            transaction.ScheduledTransactionId = scheduledTransaction.Id;

                            transaction.AuthorizedPersonAliasId = scheduledTransaction.AuthorizedPersonAliasId;
                            History.EvaluateChange( txnChanges, "Person", string.Empty, scheduledTransaction.AuthorizedPersonAlias.Person.FullName );
                            txnPersonNames.Add( transaction.Guid, scheduledTransaction.AuthorizedPersonAlias.Person.FullName );

                            transaction.FinancialGatewayId = gateway.Id;
                            History.EvaluateChange( txnChanges, "Gateway", string.Empty, gateway.Name );

                            transaction.TransactionTypeValueId = contributionTxnType.Id;
                            History.EvaluateChange( txnChanges, "Type", string.Empty, contributionTxnType.Value );

                            var currencyTypeValue = payment.CurrencyTypeValue;
                            var creditCardTypevalue = payment.CreditCardTypeValue;

                            if ( scheduledTransaction.FinancialPaymentDetail != null )
                            {
                                if ( currencyTypeValue == null && scheduledTransaction.FinancialPaymentDetail.CurrencyTypeValueId.HasValue )
                                {
                                    currencyTypeValue = DefinedValueCache.Read( scheduledTransaction.FinancialPaymentDetail.CurrencyTypeValueId.Value );
                                }

                                if ( creditCardTypevalue == null && scheduledTransaction.FinancialPaymentDetail.CreditCardTypeValueId.HasValue )
                                {
                                    creditCardTypevalue = DefinedValueCache.Read( scheduledTransaction.FinancialPaymentDetail.CreditCardTypeValueId.Value );
                                }

                                transaction.FinancialPaymentDetail.AccountNumberMasked = scheduledTransaction.FinancialPaymentDetail.AccountNumberMasked;
                                transaction.FinancialPaymentDetail.NameOnCardEncrypted = scheduledTransaction.FinancialPaymentDetail.NameOnCardEncrypted;
                                transaction.FinancialPaymentDetail.ExpirationMonthEncrypted = scheduledTransaction.FinancialPaymentDetail.ExpirationMonthEncrypted;
                                transaction.FinancialPaymentDetail.ExpirationYearEncrypted = scheduledTransaction.FinancialPaymentDetail.ExpirationYearEncrypted;
                            }

                            if ( currencyTypeValue != null )
                            {
                                transaction.FinancialPaymentDetail.CurrencyTypeValueId = currencyTypeValue.Id;
                                History.EvaluateChange( txnChanges, "Currency Type", string.Empty, currencyTypeValue.Value );
                            }
                            if ( creditCardTypevalue != null )
                            {
                                transaction.FinancialPaymentDetail.CreditCardTypeValueId = creditCardTypevalue.Id;
                                History.EvaluateChange( txnChanges, "Credit Card Type", string.Empty, creditCardTypevalue.Value );
                            }

                            //transaction.SourceTypeValueId = DefinedValueCache.Read( sourceGuid ).Id;

                            // Try to allocate the amount of the transaction based on the current scheduled transaction accounts
                            decimal remainingAmount = payment.Amount;
                            foreach ( var detail in scheduledTransaction.ScheduledTransactionDetails.Where( d => d.Amount != 0.0M ) )
                            {
                                var transactionDetail = new FinancialTransactionDetail();
                                transactionDetail.AccountId = detail.AccountId;

                                if ( detail.Amount <= remainingAmount )
                                {
                                    // If the configured amount for this account is less than or equal to the remaining
                                    // amount, allocate the configured amount
                                    transactionDetail.Amount = detail.Amount;
                                    remainingAmount -= detail.Amount;
                                }
                                else
                                {
                                    // If the configured amount is greater than the remaining amount, only allocate
                                    // the remaining amount
                                    transaction.Summary = "Note: Downloaded transaction amount was less than the configured allocation amounts for the Scheduled Transaction.";
                                    detail.Amount = remainingAmount;
                                    detail.Summary = "Note: The downloaded amount was not enough to apply the configured amount to this account.";
                                    remainingAmount = 0.0M;
                                }

                                transaction.TransactionDetails.Add( transactionDetail );

                                History.EvaluateChange( txnChanges, detail.Account.Name, 0.0M.ToString( "C2" ), transactionDetail.Amount.ToString( "C2" ) );
                                History.EvaluateChange( txnChanges, "Summary", string.Empty, transactionDetail.Summary );

                                if ( remainingAmount <= 0.0M )
                                {
                                    // If there's no amount left, break out of details
                                    break;
                                }
                            }

                            // If there's still amount left after allocating based on current config, add the remainder
                            // to the account that was configured for the most amount
                            if ( remainingAmount > 0.0M )
                            {
                                transaction.Summary = "Note: Downloaded transaction amount was greater than the configured allocation amounts for the Scheduled Transaction.";
                                var transactionDetail = transaction.TransactionDetails
                                    .OrderByDescending( d => d.Amount )
                                    .First();
                                if ( transactionDetail == null && defaultAccount != null )
                                {
                                    transactionDetail = new FinancialTransactionDetail();
                                    transactionDetail.AccountId = defaultAccount.Id;
                                }
                                if ( transactionDetail != null )
                                {
                                    transactionDetail.Amount += remainingAmount;
                                    transactionDetail.Summary = "Note: Extra amount was applied to this account.";
                                }

                                History.EvaluateChange( txnChanges, defaultAccount.Name, 0.0M.ToString( "C2" ), transactionDetail.Amount.ToString( "C2" ) );
                                History.EvaluateChange( txnChanges, "Summary", string.Empty, transactionDetail.Summary );
                            }

                            // Get the batch
                            var batch = batchService.Get(
                                batchNamePrefix,
                                currencyTypeValue,
                                creditCardTypevalue,
                                transaction.TransactionDateTime.Value,
                                gateway.GetBatchTimeOffset(),
                                batches );

                            var batchChanges = new List<string>();
                            if ( batch.Id != 0 )
                            {
                                initialControlAmounts.AddOrIgnore( batch.Guid, batch.ControlAmount );
                            }
                            batch.ControlAmount += transaction.TotalAmount;

                            batch.Transactions.Add( transaction );

                            // Add summary
                            if ( !batchSummary.ContainsKey( batch.Guid ) )
                            {
                                batchSummary.Add( batch.Guid, new List<Payment>() );
                            }
                            batchSummary[batch.Guid].Add( payment );

                            totalAdded++;
                        }
                        else
                        {
                            totalNoScheduledTransaction++;
                        }
                    }
                    else
                    {
                        totalAlreadyDownloaded++;
                    }
                }

                foreach ( var batch in batches )
                {
                    var batchChanges = new List<string>();
                    allBatchChanges.Add( batch.Guid, batchChanges );

                    if ( batch.Id == 0 )
                    {
                        batchChanges.Add( "Generated the batch" );
                        History.EvaluateChange( batchChanges, "Batch Name", string.Empty, batch.Name );
                        History.EvaluateChange( batchChanges, "Status", null, batch.Status );
                        History.EvaluateChange( batchChanges, "Start Date/Time", null, batch.BatchStartDateTime );
                        History.EvaluateChange( batchChanges, "End Date/Time", null, batch.BatchEndDateTime );
                    }

                    if ( initialControlAmounts.ContainsKey( batch.Guid ) )
                    {
                        History.EvaluateChange( batchChanges, "Control Amount", initialControlAmounts[batch.Guid].ToString( "C2" ), batch.ControlAmount.ToString( "C2" ) );
                    }
                }

                rockContext.WrapTransaction( () =>
                {
                    rockContext.SaveChanges();

                    foreach ( var batch in batches )
                    {
                        HistoryService.SaveChanges(
                            rockContext,
                            typeof( FinancialBatch ),
                            Rock.SystemGuid.Category.HISTORY_FINANCIAL_BATCH.AsGuid(),
                            batch.Id,
                            allBatchChanges[batch.Guid]
                        );

                        foreach ( var transaction in batch.Transactions )
                        {
                            if ( allTxnChanges.ContainsKey( transaction.Guid ) )
                            {
                                HistoryService.SaveChanges(
                                    rockContext,
                                    typeof( FinancialBatch ),
                                    Rock.SystemGuid.Category.HISTORY_FINANCIAL_TRANSACTION.AsGuid(),
                                    batch.Id,
                                    allTxnChanges[transaction.Guid],
                                    txnPersonNames[transaction.Guid],
                                    typeof( FinancialTransaction ),
                                    transaction.Id
                                );
                            }
                        }
                    }

                    rockContext.SaveChanges();
                } );
            }

            StringBuilder sb = new StringBuilder();
            sb.AppendFormat( "<li>{0} {1} downloaded.</li>", totalPayments.ToString( "N0" ),
                ( totalPayments == 1 ? "payment" : "payments" ) );

            if ( totalAlreadyDownloaded > 0 )
            {
                sb.AppendFormat( "<li>{0} {1} previously downloaded and {2} already been added.</li>", totalAlreadyDownloaded.ToString( "N0" ),
                    ( totalAlreadyDownloaded == 1 ? "payment was" : "payments were" ),
                    ( totalAlreadyDownloaded == 1 ? "has" : "have" ) );
            }

            if ( totalNoScheduledTransaction > 0 )
            {
                sb.AppendFormat( "<li>{0} {1} could not be matched to an existing scheduled payment profile.</li>", totalNoScheduledTransaction.ToString( "N0" ),
                    ( totalNoScheduledTransaction == 1 ? "payment" : "payments" ) );
            }

            sb.AppendFormat( "<li>{0} {1} successfully added.</li>", totalAdded.ToString( "N0" ),
                ( totalAdded == 1 ? "payment was" : "payments were" ) );

            foreach ( var batchItem in batchSummary )
            {
                int items = batchItem.Value.Count;
                if (items > 0)
                {
                    var batch = batches
                        .Where( b => b.Guid.Equals( batchItem.Key ) )
                        .FirstOrDefault();

                    string batchName = string.Format("'{0} ({1})'", batch.Name, batch.BatchStartDateTime.Value.ToString("d"));
                    if ( !string.IsNullOrWhiteSpace( batchUrlFormat ) )
                    {
                        batchName = string.Format( "<a href='{0}'>{1}</a>", string.Format( batchUrlFormat, batch.Id ), batchName );
                    }

                    decimal sum = batchItem.Value.Select( p => p.Amount ).Sum();

                    string summaryformat = items == 1 ?
                        "<li>{0} transaction of {1} was added to the {2} batch.</li>" :
                        "<li>{0} transactions totaling {1} were added to the {2} batch</li>";

                    sb.AppendFormat( summaryformat, items.ToString( "N0" ), sum.ToString( "C2" ), batchName );
                }
            }

            return sb.ToString();
        }
Beispiel #9
0
        /// <summary>
        /// Processes the confirmation.
        /// </summary>
        /// <param name="errorMessage">The error message.</param>
        /// <returns></returns>
        private bool ProcessConfirmation( out string errorMessage )
        {
            if ( string.IsNullOrWhiteSpace( TransactionCode ) )
            {
                GatewayComponent gateway = hfPaymentTab.Value == "ACH" ? _achGateway : _ccGateway;
                if ( gateway == null )
                {
                    errorMessage = "There was a problem creating the payment gateway information";
                    return false;
                }

                Person person = GetPerson( true );
                if ( person == null )
                {
                    errorMessage = "There was a problem creating the person information";
                    return false;
                }

                PaymentInfo paymentInfo = GetPaymentInfo();
                if ( paymentInfo == null )
                {
                    errorMessage = "There was a problem creating the payment information";
                    return false;
                }
                else
                {
                    paymentInfo.FirstName = person.FirstName;
                    paymentInfo.LastName = person.LastName;
                }

                if ( paymentInfo.CreditCardTypeValue != null )
                {
                    CreditCardTypeValueId = paymentInfo.CreditCardTypeValue.Id;
                } 

                PaymentSchedule schedule = GetSchedule();
                if ( schedule != null )
                {
                    schedule.PersonId = person.Id;

                    var scheduledTransaction = gateway.AddScheduledPayment( schedule, paymentInfo, out errorMessage );
                    if ( scheduledTransaction != null )
                    {
                        scheduledTransaction.TransactionFrequencyValueId = schedule.TransactionFrequencyValue.Id;
                        scheduledTransaction.AuthorizedPersonId = person.Id;
                        scheduledTransaction.GatewayEntityTypeId = EntityTypeCache.Read( gateway.TypeGuid ).Id;

                        foreach ( var account in SelectedAccounts.Where( a => a.Amount > 0 ) )
                        {
                            var transactionDetail = new FinancialScheduledTransactionDetail();
                            transactionDetail.Amount = account.Amount;
                            transactionDetail.AccountId = account.Id;
                            scheduledTransaction.ScheduledTransactionDetails.Add( transactionDetail );
                        }

                        var transactionService = new FinancialScheduledTransactionService();
                        transactionService.Add( scheduledTransaction, CurrentPersonId );
                        transactionService.Save( scheduledTransaction, CurrentPersonId );

                        ScheduleId = scheduledTransaction.GatewayScheduleId;
                        TransactionCode = scheduledTransaction.TransactionCode;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    var transaction = gateway.Charge( paymentInfo, out errorMessage );
                    if ( transaction != null )
                    {
                        transaction.TransactionDateTime = DateTime.Now;
                        transaction.AuthorizedPersonId = person.Id;
                        transaction.GatewayEntityTypeId = gateway.TypeId;
                        transaction.Amount = paymentInfo.Amount;
                        transaction.TransactionTypeValueId = DefinedValueCache.Read(new Guid(Rock.SystemGuid.DefinedValue.TRANSACTION_TYPE_CONTRIBUTION)).Id;
                        transaction.CurrencyTypeValueId = paymentInfo.CurrencyTypeValue.Id;
                        transaction.CreditCardTypeValueId = CreditCardTypeValueId;

                        Guid sourceGuid = Guid.Empty;
                        if (Guid.TryParse(GetAttributeValue("Source"), out sourceGuid))
                        {
                            transaction.SourceTypeValueId = DefinedValueCache.Read(sourceGuid).Id;
                        }
                        foreach ( var account in SelectedAccounts.Where( a => a.Amount > 0 ) )
                        {
                            var transactionDetail = new FinancialTransactionDetail();
                            transactionDetail.Amount = account.Amount;
                            transactionDetail.AccountId = account.Id;
                            transaction.TransactionDetails.Add( transactionDetail );
                        }

                        // Get the batch name
                        string ccSuffix = string.Empty;
                        if ( paymentInfo.CreditCardTypeValue != null )
                        {
                            ccSuffix = paymentInfo.CreditCardTypeValue.GetAttributeValue( "BatchNameSuffix" );
                        }
                        if ( string.IsNullOrWhiteSpace( ccSuffix ) )
                        {
                            ccSuffix = paymentInfo.CurrencyTypeValue.Name;
                        }
                        string batchName = GetAttributeValue( "BatchNamePrefix" ).Trim() + " " + ccSuffix;

                        using ( new UnitOfWorkScope() )
                        {
                            var batchService = new FinancialBatchService();
                            var batch = batchService.Queryable()
                                .Where( b =>
                                    b.Status == BatchStatus.Open &&
                                    b.BatchStartDateTime <= transaction.TransactionDateTime &&
                                    b.BatchEndDateTime > transaction.TransactionDateTime &&
                                    b.Name == batchName )
                                .FirstOrDefault();
                            if ( batch == null )
                            {
                                batch = new FinancialBatch();
                                batch.Name = batchName;
                                batch.Status = BatchStatus.Open;
                                batch.BatchStartDateTime = transaction.TransactionDateTime.Value.Date.Add( gateway.BatchTimeOffset );
                                if ( batch.BatchStartDateTime > transaction.TransactionDateTime )
                                {
                                    batch.BatchStartDateTime.Value.AddDays( -1 );
                                }
                                batch.BatchEndDateTime = batch.BatchStartDateTime.Value.AddDays( 1 ).AddMilliseconds( -1 );
                                batch.CreatedByPersonId = person.Id;
                                batchService.Add( batch, CurrentPersonId );
                                batchService.Save( batch, CurrentPersonId );

                                batch = batchService.Get( batch.Id );
                            }

                            batch.ControlAmount += transaction.Amount;
                            batchService.Save( batch, CurrentPersonId );

                            var transactionService = new FinancialTransactionService();
                            transaction.BatchId = batch.Id;
                            transactionService.Add( transaction, CurrentPersonId );
                            transactionService.Save( transaction, CurrentPersonId );
                        }

                        TransactionCode = transaction.TransactionCode;
                    }
                    else
                    {
                        return false;
                    }
                }

                tdTransactionCode.Description = TransactionCode;
                tdTransactionCode.Visible = !string.IsNullOrWhiteSpace( TransactionCode );

                tdScheduleId.Description = ScheduleId;
                tdScheduleId.Visible = !string.IsNullOrWhiteSpace( ScheduleId );

                // If there was a transaction code returned and this was not already created from a previous saved account, 
                // show the option to save the account.
                if ( !( paymentInfo is ReferencePaymentInfo ) && !string.IsNullOrWhiteSpace( TransactionCode ) )
                {
                    cbSaveAccount.Visible = true;
                    pnlSaveAccount.Visible = true;
                    txtSaveAccount.Visible = true;

                    // If current person does not have a login, have them create a username and password
                    phCreateLogin.Visible = !new UserLoginService().GetByPersonId( person.Id ).Any();
                }
                else
                {
                    pnlSaveAccount.Visible = false;
                }

                return true;
            }
            else
            {
                pnlDupWarning.Visible = true;
                errorMessage = string.Empty;
                return false;
            }
        }
Beispiel #10
0
        /// <summary>
        /// Processes the payments and returns a summary in HTML format
        /// </summary>
        /// <param name="gateway">The gateway.</param>
        /// <param name="batchNamePrefix">The batch name prefix.</param>
        /// <param name="payments">The payments.</param>
        /// <param name="batchUrlFormat">The batch URL format.</param>
        /// <param name="receiptEmail">The receipt email.</param>
        /// <param name="failedPaymentEmail">The failed payment email.</param>
        /// <param name="failedPaymentWorkflowType">Type of the failed payment workflow.</param>
        /// <returns></returns>
        public static string ProcessPayments(FinancialGateway gateway, string batchNamePrefix, List <Payment> payments, string batchUrlFormat,
                                             Guid?receiptEmail, Guid?failedPaymentEmail, Guid?failedPaymentWorkflowType)
        {
            int            totalPayments              = 0;
            int            totalAlreadyDownloaded     = 0;
            List <Payment> paymentsWithoutTransaction = new List <Payment>();
            int            totalAdded         = 0;
            int            totalReversals     = 0;
            int            totalFailures      = 0;
            int            totalStatusChanges = 0;

            var batchSummary = new Dictionary <Guid, List <Decimal> >();

            var newTransactionsForReceiptEmails = new List <FinancialTransaction>();

            var failedPayments = new List <FinancialTransaction>();

            var contributionTxnType = DefinedValueCache.Get(Rock.SystemGuid.DefinedValue.TRANSACTION_TYPE_CONTRIBUTION.AsGuid());

            int?defaultAccountId = null;

            using (var rockContext2 = new RockContext())
            {
                defaultAccountId = new FinancialAccountService(rockContext2).Queryable()
                                   .Where(a =>
                                          a.IsActive &&
                                          !a.ParentAccountId.HasValue &&
                                          (!a.StartDate.HasValue || a.StartDate.Value <= RockDateTime.Now) &&
                                          (!a.EndDate.HasValue || a.EndDate.Value >= RockDateTime.Now)
                                          )
                                   .OrderBy(a => a.Order)
                                   .Select(a => a.Id)
                                   .FirstOrDefault();
            }

            var batchTxnChanges         = new Dictionary <Guid, List <string> >();
            var batchBatchChanges       = new Dictionary <Guid, List <string> >();
            var scheduledTransactionIds = new List <int>();
            List <FinancialTransaction> transactionsWithAttributes = new List <FinancialTransaction>();

            foreach (var payment in payments.Where(p => p.Amount > 0.0M))
            {
                using (var rockContext = new RockContext())
                {
                    totalPayments++;

                    var financialTransactionService = new FinancialTransactionService(rockContext);

                    FinancialTransaction        originalTxn = null;
                    List <FinancialTransaction> txns        = null;

                    // Find existing payments with same transaction code as long as it is not blank.
                    if (payment.TransactionCode.IsNotNullOrWhiteSpace())
                    {
                        txns = financialTransactionService
                               .Queryable("TransactionDetails")
                               .Where(t =>
                                      t.FinancialGatewayId.HasValue &&
                                      t.FinancialGatewayId.Value == gateway.Id &&
                                      t.TransactionCode == payment.TransactionCode)
                               .ToList();

                        originalTxn = txns.Any() ? txns.OrderBy(t => t.Id).First() : null;
                    }

                    FinancialScheduledTransaction scheduledTransaction = null;

                    // We don't want to match a blank schedule ID, so if we don't have one then scheduledTransaction will stay NULL
                    if (payment.GatewayScheduleId.IsNotNullOrWhiteSpace())
                    {
                        scheduledTransaction = new FinancialScheduledTransactionService(rockContext).GetByScheduleId(payment.GatewayScheduleId, gateway.Id);
                    }

                    // Calculate whether a transaction needs to be added
                    var txnAmount = CalculateTransactionAmount(payment, txns);
                    if (txnAmount != 0.0M || (payment.IsFailure && originalTxn == null && scheduledTransaction != null))
                    {
                        // Verify that the payment is for an existing scheduled transaction, or has the same transaction code as an existing payment
                        if (scheduledTransaction != null || originalTxn != null)
                        {
                            var transaction = new FinancialTransaction();
                            transaction.Guid                   = Guid.NewGuid();
                            transaction.TransactionCode        = payment.TransactionCode;
                            transaction.TransactionDateTime    = payment.TransactionDateTime;
                            transaction.Status                 = payment.Status;
                            transaction.IsSettled              = payment.IsSettled;
                            transaction.SettledGroupId         = payment.SettledGroupId;
                            transaction.SettledDate            = payment.SettledDate;
                            transaction.StatusMessage          = payment.StatusMessage;
                            transaction.FinancialPaymentDetail = new FinancialPaymentDetail();

                            if (payment.ForeignKey.IsNotNullOrWhiteSpace())
                            {
                                transaction.ForeignKey = payment.ForeignKey;
                            }

                            FinancialPaymentDetail    financialPaymentDetail = null;
                            List <ITransactionDetail> originalTxnDetails     = new List <ITransactionDetail>();

                            if (scheduledTransaction != null)
                            {
                                scheduledTransactionIds.Add(scheduledTransaction.Id);
                                if (payment.ScheduleActive.HasValue)
                                {
                                    scheduledTransaction.IsActive = payment.ScheduleActive.Value;
                                }

                                transaction.ScheduledTransactionId  = scheduledTransaction.Id;
                                transaction.AuthorizedPersonAliasId = scheduledTransaction.AuthorizedPersonAliasId;
                                transaction.SourceTypeValueId       = scheduledTransaction.SourceTypeValueId;
                                financialPaymentDetail = scheduledTransaction.FinancialPaymentDetail;
                                scheduledTransaction.ScheduledTransactionDetails.ToList().ForEach(d => originalTxnDetails.Add(d));
                            }
                            else
                            {
                                transaction.AuthorizedPersonAliasId = originalTxn.AuthorizedPersonAliasId;
                                transaction.SourceTypeValueId       = originalTxn.SourceTypeValueId;
                                financialPaymentDetail = originalTxn.FinancialPaymentDetail;
                                originalTxn.TransactionDetails.ToList().ForEach(d => originalTxnDetails.Add(d));
                            }

                            transaction.FinancialGatewayId     = gateway.Id;
                            transaction.TransactionTypeValueId = contributionTxnType.Id;

                            if (txnAmount < 0.0M)
                            {
                                transaction.Summary = "Reversal created for previous transaction(s) to correct the total transaction amount." + Environment.NewLine;
                            }

                            // Set the attributes of the transaction
                            if (payment.Attributes != null && payment.Attributes.Count > 0)
                            {
                                transaction.LoadAttributes();
                                foreach (var attribute in payment.Attributes)
                                {
                                    transaction.SetAttributeValue(attribute.Key, attribute.Value);
                                }
                                transactionsWithAttributes.Add(transaction);
                            }

                            var currencyTypeValue   = payment.CurrencyTypeValue;
                            var creditCardTypevalue = payment.CreditCardTypeValue;

                            if (financialPaymentDetail != null)
                            {
                                if (currencyTypeValue == null && financialPaymentDetail.CurrencyTypeValueId.HasValue)
                                {
                                    currencyTypeValue = DefinedValueCache.Get(financialPaymentDetail.CurrencyTypeValueId.Value);
                                }

                                if (creditCardTypevalue == null && financialPaymentDetail.CreditCardTypeValueId.HasValue)
                                {
                                    creditCardTypevalue = DefinedValueCache.Get(financialPaymentDetail.CreditCardTypeValueId.Value);
                                }

                                transaction.FinancialPaymentDetail.AccountNumberMasked      = financialPaymentDetail.AccountNumberMasked;
                                transaction.FinancialPaymentDetail.NameOnCardEncrypted      = financialPaymentDetail.NameOnCardEncrypted;
                                transaction.FinancialPaymentDetail.ExpirationMonthEncrypted = financialPaymentDetail.ExpirationMonthEncrypted;
                                transaction.FinancialPaymentDetail.ExpirationYearEncrypted  = financialPaymentDetail.ExpirationYearEncrypted;
                                transaction.FinancialPaymentDetail.BillingLocationId        = financialPaymentDetail.BillingLocationId;
                                if (financialPaymentDetail.GatewayPersonIdentifier.IsNullOrWhiteSpace())
                                {
                                    // if Rock doesn't have the GatewayPersonIdentifier, get it from the downloaded payment (if it has a value)
                                    transaction.FinancialPaymentDetail.GatewayPersonIdentifier = payment.GatewayPersonIdentifier;
                                }
                                else
                                {
                                    transaction.FinancialPaymentDetail.GatewayPersonIdentifier = financialPaymentDetail.GatewayPersonIdentifier;
                                }

                                transaction.FinancialPaymentDetail.FinancialPersonSavedAccountId = financialPaymentDetail.FinancialPersonSavedAccountId;
                            }

                            if (currencyTypeValue != null)
                            {
                                transaction.FinancialPaymentDetail.CurrencyTypeValueId = currencyTypeValue.Id;
                            }
                            if (creditCardTypevalue != null)
                            {
                                transaction.FinancialPaymentDetail.CreditCardTypeValueId = creditCardTypevalue.Id;
                            }

                            // Try to allocate the amount of the transaction based on the current scheduled transaction accounts
                            decimal remainingAmount = Math.Abs(txnAmount);
                            foreach (var detail in originalTxnDetails.Where(d => d.Amount != 0.0M))
                            {
                                if (remainingAmount <= 0.0M)
                                {
                                    // If there's no amount left, break out of details
                                    break;
                                }

                                var transactionDetail = new FinancialTransactionDetail();
                                transactionDetail.AccountId    = detail.AccountId;
                                transactionDetail.EntityTypeId = detail.EntityTypeId;
                                transactionDetail.EntityId     = detail.EntityId;

                                if (detail.Amount <= remainingAmount)
                                {
                                    // If the configured amount for this account is less than or equal to the remaining
                                    // amount, allocate the configured amount
                                    transactionDetail.Amount = detail.Amount;
                                    remainingAmount         -= detail.Amount;
                                }
                                else
                                {
                                    // If the configured amount is greater than the remaining amount, only allocate
                                    // the remaining amount
                                    transaction.Summary      += "Note: Downloaded transaction amount was less than the configured allocation amounts for the Scheduled Transaction.";
                                    transactionDetail.Amount  = remainingAmount;
                                    transactionDetail.Summary = "Note: The downloaded amount was not enough to apply the configured amount to this account.";
                                    remainingAmount           = 0.0M;
                                }

                                transaction.TransactionDetails.Add(transactionDetail);
                            }

                            // If there's still amount left after allocating based on current config, add the remainder
                            // to the account that was configured for the most amount
                            if (remainingAmount > 0.0M)
                            {
                                transaction.Summary += "Note: Downloaded transaction amount was greater than the configured allocation amounts for the Scheduled Transaction.";
                                var transactionDetail = transaction.TransactionDetails
                                                        .OrderByDescending(d => d.Amount)
                                                        .FirstOrDefault();
                                if (transactionDetail == null && defaultAccountId.HasValue)
                                {
                                    transactionDetail           = new FinancialTransactionDetail();
                                    transactionDetail.AccountId = defaultAccountId.Value;
                                    transaction.TransactionDetails.Add(transactionDetail);
                                }
                                if (transactionDetail != null)
                                {
                                    transactionDetail.Amount += remainingAmount;
                                    transactionDetail.Summary = "Note: Extra amount was applied to this account.";
                                }
                            }

                            // If the amount to apply was negative, update all details to be negative (absolute value was used when allocating to accounts)
                            if (txnAmount < 0.0M)
                            {
                                foreach (var txnDetail in transaction.TransactionDetails)
                                {
                                    txnDetail.Amount = 0 - txnDetail.Amount;
                                }
                            }

                            // Get the batch
                            var batchService = new FinancialBatchService(rockContext);
                            var batch        = batchService.Get(
                                batchNamePrefix,
                                string.Empty,
                                currencyTypeValue,
                                creditCardTypevalue,
                                transaction.TransactionDateTime.Value,
                                gateway.GetBatchTimeOffset(),
                                gateway.BatchDayOfWeek);

                            if (batch.Id == 0)
                            {
                                // get a batch Id
                                rockContext.SaveChanges();
                            }

                            transaction.BatchId = batch.Id;
                            financialTransactionService.Add(transaction);
                            batchService.IncrementControlAmount(batch.Id, transaction.TotalAmount, null);

                            if (receiptEmail.HasValue && txnAmount > 0.0M)
                            {
                                newTransactionsForReceiptEmails.Add(transaction);
                            }

                            if (
                                payment.IsFailure &&
                                (
                                    (txnAmount == 0.0M && scheduledTransaction != null && originalTxn == null) ||
                                    (txnAmount < 0.0M && originalTxn != null)
                                ))
                            {
                                failedPayments.Add(transaction);
                            }

                            // Add summary
                            if (!batchSummary.ContainsKey(batch.Guid))
                            {
                                batchSummary.Add(batch.Guid, new List <Decimal>());
                            }
                            batchSummary[batch.Guid].Add(txnAmount);

                            totalAdded++;

                            if (txnAmount < 0.0M)
                            {
                                totalReversals++;
                            }
                            else if (txnAmount == 0.0M)
                            {
                                totalFailures++;
                            }
                        }
                        else
                        {
                            paymentsWithoutTransaction.Add(payment);
                        }
                    }
                    else
                    {
                        totalAlreadyDownloaded++;
                    }

                    if (txns != null)
                    {
                        foreach (var txn in txns
                                 .Where(t =>
                                        t.Status != payment.Status ||
                                        t.StatusMessage != payment.StatusMessage ||
                                        t.IsSettled != payment.IsSettled ||
                                        t.SettledGroupId != payment.SettledGroupId ||
                                        t.SettledDate != payment.SettledDate))
                        {
                            txn.IsSettled      = payment.IsSettled;
                            txn.SettledGroupId = payment.SettledGroupId;
                            txn.SettledDate    = payment.SettledDate;
                            txn.Status         = payment.Status;
                            txn.StatusMessage  = payment.StatusMessage;
                            totalStatusChanges++;
                        }
                    }

                    rockContext.SaveChanges();
                }
            }
            if (transactionsWithAttributes.Count > 0)
            {
                foreach (var transaction in transactionsWithAttributes)
                {
                    using (var rockContext3 = new RockContext())
                    {
                        transaction.SaveAttributeValues(rockContext3);
                        rockContext3.SaveChanges();
                    }
                }
            }

            // Queue a transaction to update the status of all affected scheduled transactions
            var updatePaymentStatusTxn = new Rock.Transactions.UpdatePaymentStatusTransaction(gateway.Id, scheduledTransactionIds);

            Rock.Transactions.RockQueue.TransactionQueue.Enqueue(updatePaymentStatusTxn);

            if (receiptEmail.HasValue && newTransactionsForReceiptEmails.Any())
            {
                // Queue a transaction to send receipts
                var newTransactionIds      = newTransactionsForReceiptEmails.Select(t => t.Id).ToList();
                var sendPaymentReceiptsTxn = new Rock.Transactions.SendPaymentReceipts(receiptEmail.Value, newTransactionIds);
                Rock.Transactions.RockQueue.TransactionQueue.Enqueue(sendPaymentReceiptsTxn);
            }

            // Queue transactions to launch failed payment workflow
            if (failedPayments.Any())
            {
                if (failedPaymentEmail.HasValue)
                {
                    // Queue a transaction to send payment failure
                    var newTransactionIds     = failedPayments.Select(t => t.Id).ToList();
                    var sendPaymentFailureTxn = new Rock.Transactions.SendPaymentReceipts(failedPaymentEmail.Value, newTransactionIds);
                    Rock.Transactions.RockQueue.TransactionQueue.Enqueue(sendPaymentFailureTxn);
                }

                if (failedPaymentWorkflowType.HasValue)
                {
                    // Queue a transaction to launch workflow
                    var workflowDetails    = failedPayments.Select(p => new LaunchWorkflowDetails(p)).ToList();
                    var launchWorkflowsTxn = new Rock.Transactions.LaunchWorkflowsTransaction(failedPaymentWorkflowType.Value, workflowDetails);
                    Rock.Transactions.RockQueue.TransactionQueue.Enqueue(launchWorkflowsTxn);
                }
            }

            StringBuilder sb = new StringBuilder();

            sb.AppendFormat("<li>{0} {1} downloaded.</li>", totalPayments.ToString("N0"),
                            (totalPayments == 1 ? "payment" : "payments"));

            if (totalAlreadyDownloaded > 0)
            {
                sb.AppendFormat("<li>{0} {1} previously downloaded and {2} already been added.</li>", totalAlreadyDownloaded.ToString("N0"),
                                (totalAlreadyDownloaded == 1 ? "payment was" : "payments were"),
                                (totalAlreadyDownloaded == 1 ? "has" : "have"));
            }

            if (totalStatusChanges > 0)
            {
                sb.AppendFormat("<li>{0} {1} previously downloaded but had a change of status.</li>", totalStatusChanges.ToString("N0"),
                                (totalStatusChanges == 1 ? "payment was" : "payments were"));
            }

            if (paymentsWithoutTransaction.Any())
            {
                var scheduledPaymentList = paymentsWithoutTransaction.Where(a => a.GatewayScheduleId.IsNotNullOrWhiteSpace()).Select(a => a.GatewayScheduleId).ToList();
                if (scheduledPaymentList.Any())
                {
                    sb.Append($@"<li>The following {scheduledPaymentList.Count.ToString( "N0" )} gateway payments could not be matched to an existing scheduled payment profile:
<pre>{scheduledPaymentList.AsDelimited( "\n" )}</pre>
</li>");
                }

                var previousTransactionList = paymentsWithoutTransaction.Where(a => a.GatewayScheduleId.IsNullOrWhiteSpace()).Select(a => a.TransactionCode).ToList();

                if (previousTransactionList.Any())
                {
                    sb.Append($@"<li>The following {previousTransactionList.Count.ToString( "N0" )} gateway payments could not be matched to a previous transaction:
<pre>{previousTransactionList.AsDelimited( "\n" )}</pre>
</li>");
                }
            }

            sb.AppendFormat("<li>{0} {1} added.</li>", totalAdded.ToString("N0"),
                            (totalAdded == 1 ? "new payment was" : "new payments were"));

            if (totalReversals > 0)
            {
                sb.AppendFormat("<li>{0} {1} added as a reversal to a previous transaction.</li>", totalReversals.ToString("N0"),
                                (totalReversals == 1 ? "payment was" : "payments were"));
            }

            if (totalFailures > 0)
            {
                sb.AppendFormat("<li>{0} {1} recorded as a failed transaction.</li>", totalFailures.ToString("N0"),
                                (totalFailures == 1 ? "payment was" : "payments were"));
            }

            using (var rockContext = new RockContext())
            {
                var batches = new FinancialBatchService(rockContext)
                              .Queryable().AsNoTracking()
                              .Where(b => batchSummary.Keys.Contains(b.Guid))
                              .ToList();

                foreach (var batchItem in batchSummary)
                {
                    int items = batchItem.Value.Count;
                    if (items > 0)
                    {
                        var batch = batches
                                    .Where(b => b.Guid.Equals(batchItem.Key))
                                    .FirstOrDefault();

                        string batchName = string.Format("'{0} ({1})'", batch.Name, batch.BatchStartDateTime.Value.ToString("d"));
                        if (!string.IsNullOrWhiteSpace(batchUrlFormat))
                        {
                            batchName = string.Format("<a href='{0}'>{1}</a>", string.Format(batchUrlFormat, batch.Id), batchName);
                        }

                        decimal sum = batchItem.Value.Sum();

                        string summaryformat = items == 1 ?
                                               "<li>{0} transaction of {1} was added to the {2} batch.</li>" :
                                               "<li>{0} transactions totaling {1} were added to the {2} batch</li>";

                        sb.AppendFormat(summaryformat, items.ToString("N0"), sum.FormatAsCurrency(), batchName);
                    }
                }
            }

            return(sb.ToString());
        }
        /// <summary>
        /// Handles the Click event of the lbCancel control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void lbCancel_Click( object sender, EventArgs e )
        {
            int? txnId = PageParameter( "ScheduledTransactionId" ).AsIntegerOrNull();
            if ( txnId.HasValue )
            {
                var rockContext = new RockContext();
                var txnService = new FinancialScheduledTransactionService( rockContext );
                var txn = txnService.Get( txnId.Value );
                if ( txn != null )
                {
                    string errorMessage = string.Empty;
                    if ( txnService.Cancel( txn, out errorMessage ) )
                    {
                        txnService.GetStatus( txn, out errorMessage );
                        rockContext.SaveChanges();
                    }
                    else
                    {
                        ShowErrorMessage( errorMessage );
                    }

                    ShowView( txn );
                }
            }
        }
        /// <summary>
        /// Processes the confirmation.
        /// </summary>
        /// <param name="errorMessage">The error message.</param>
        /// <returns></returns>
        private bool ProcessConfirmation( out string errorMessage )
        {
            var rockContext = new RockContext();
            if ( string.IsNullOrWhiteSpace( TransactionCode ) )
            {
                GatewayComponent gateway = null;
                var financialGateway = hfPaymentTab.Value == "ACH" ? _achGateway : _ccGateway;
                if ( financialGateway != null )
                {
                    gateway = financialGateway.GetGatewayComponent();
                }

                if ( gateway == null )
                {
                    errorMessage = "There was a problem creating the payment gateway information";
                    return false;
                }

                Person person = GetPerson( true );
                if ( person == null )
                {
                    errorMessage = "There was a problem creating the person information";
                    return false;
                }

                if ( !person.PrimaryAliasId.HasValue )
                {
                    errorMessage = "There was a problem creating the person's primary alias";
                    return false;
                }

                PaymentInfo paymentInfo = GetPaymentInfo();
                if ( paymentInfo == null )
                {
                    errorMessage = "There was a problem creating the payment information";
                    return false;
                }
                else
                {
                    paymentInfo.FirstName = person.FirstName;
                    paymentInfo.LastName = person.LastName;
                }

                if ( paymentInfo.CreditCardTypeValue != null )
                {
                    CreditCardTypeValueId = paymentInfo.CreditCardTypeValue.Id;
                }

                if ( _showCommmentEntry ) {
                    paymentInfo.Comment1 = !string.IsNullOrWhiteSpace(GetAttributeValue( "PaymentComment" )) ? string.Format("{0}: {1}", GetAttributeValue( "PaymentComment" ), txtCommentEntry.Text ) : txtCommentEntry.Text;
                }
                else
                {
                    paymentInfo.Comment1 = GetAttributeValue( "PaymentComment" );
                }

                PaymentSchedule schedule = GetSchedule();
                if ( schedule != null )
                {
                    schedule.PersonId = person.Id;

                    var scheduledTransaction = gateway.AddScheduledPayment( financialGateway, schedule, paymentInfo, out errorMessage );
                    if ( scheduledTransaction != null )
                    {
                        scheduledTransaction.TransactionFrequencyValueId = schedule.TransactionFrequencyValue.Id;
                        scheduledTransaction.AuthorizedPersonAliasId = person.PrimaryAliasId.Value;
                        scheduledTransaction.FinancialGatewayId = financialGateway.Id;

                        if ( scheduledTransaction.FinancialPaymentDetail == null )
                        {
                            scheduledTransaction.FinancialPaymentDetail = new FinancialPaymentDetail();
                        }
                        scheduledTransaction.FinancialPaymentDetail.SetFromPaymentInfo( paymentInfo, gateway, rockContext );

                        var changeSummary = new StringBuilder();
                        changeSummary.AppendFormat( "{0} starting {1}", schedule.TransactionFrequencyValue.Value, schedule.StartDate.ToShortDateString() );
                        changeSummary.AppendLine();
                        changeSummary.Append( paymentInfo.CurrencyTypeValue.Value );
                        if ( paymentInfo.CreditCardTypeValue != null )
                        {
                            changeSummary.AppendFormat( " - {0}", paymentInfo.CreditCardTypeValue.Value );
                        }
                        changeSummary.AppendFormat( " {0}", paymentInfo.MaskedNumber );
                        changeSummary.AppendLine();

                        foreach ( var account in SelectedAccounts.Where( a => a.Amount > 0 ) )
                        {
                            var transactionDetail = new FinancialScheduledTransactionDetail();
                            transactionDetail.Amount = account.Amount;
                            transactionDetail.AccountId = account.Id;
                            scheduledTransaction.ScheduledTransactionDetails.Add( transactionDetail );
                            changeSummary.AppendFormat( "{0}: {1}", account.Name, account.Amount.FormatAsCurrency() );
                            changeSummary.AppendLine();
                        }

                        var transactionService = new FinancialScheduledTransactionService( rockContext );
                        transactionService.Add( scheduledTransaction );
                        rockContext.SaveChanges();

                        // Add a note about the change
                        var noteType = NoteTypeCache.Read( Rock.SystemGuid.NoteType.SCHEDULED_TRANSACTION_NOTE.AsGuid() );
                        if ( noteType != null )
                        {
                            var noteService = new NoteService( rockContext );
                            var note = new Note();
                            note.NoteTypeId = noteType.Id;
                            note.EntityId = scheduledTransaction.Id;
                            note.Caption = "Created Transaction";
                            note.Text = changeSummary.ToString();
                            noteService.Add( note );
                        }
                        rockContext.SaveChanges();

                        ScheduleId = scheduledTransaction.GatewayScheduleId;
                        TransactionCode = scheduledTransaction.TransactionCode;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    var transaction = gateway.Charge( financialGateway, paymentInfo, out errorMessage );
                    if ( transaction != null )
                    {
                        var txnChanges = new List<string>();
                        txnChanges.Add( "Created Transaction" );

                        History.EvaluateChange( txnChanges, "Transaction Code", string.Empty, transaction.TransactionCode );

                        transaction.AuthorizedPersonAliasId = person.PrimaryAliasId;
                        History.EvaluateChange( txnChanges, "Person", string.Empty, person.FullName );

                        transaction.TransactionDateTime = RockDateTime.Now;
                        History.EvaluateChange( txnChanges, "Date/Time", null, transaction.TransactionDateTime );

                        transaction.FinancialGatewayId = financialGateway.Id;
                        History.EvaluateChange( txnChanges, "Gateway", string.Empty, financialGateway.Name );

                        var txnType = DefinedValueCache.Read( new Guid( Rock.SystemGuid.DefinedValue.TRANSACTION_TYPE_CONTRIBUTION ) );
                        transaction.TransactionTypeValueId = txnType.Id;
                        History.EvaluateChange( txnChanges, "Type", string.Empty, txnType.Value );

                        if ( transaction.FinancialPaymentDetail == null )
                        {
                            transaction.FinancialPaymentDetail = new FinancialPaymentDetail();
                        }
                        transaction.FinancialPaymentDetail.SetFromPaymentInfo( paymentInfo, gateway, rockContext, txnChanges );

                        Guid sourceGuid = Guid.Empty;
                        if ( Guid.TryParse( GetAttributeValue( "Source" ), out sourceGuid ) )
                        {
                            var source = DefinedValueCache.Read( sourceGuid );
                            if ( source != null )
                            {
                                transaction.SourceTypeValueId = source.Id;
                                History.EvaluateChange( txnChanges, "Source", string.Empty, source.Value );
                            }
                        }

                        foreach ( var account in SelectedAccounts.Where( a => a.Amount > 0 ) )
                        {
                            var transactionDetail = new FinancialTransactionDetail();
                            transactionDetail.Amount = account.Amount;
                            transactionDetail.AccountId = account.Id;
                            transaction.TransactionDetails.Add( transactionDetail );
                            History.EvaluateChange( txnChanges, account.Name, 0.0M.FormatAsCurrency(), transactionDetail.Amount.FormatAsCurrency() );
                        }

                        var batchService = new FinancialBatchService( rockContext );

                        // Get the batch
                        var batch = batchService.Get(
                            GetAttributeValue( "BatchNamePrefix" ),
                            paymentInfo.CurrencyTypeValue,
                            paymentInfo.CreditCardTypeValue,
                            transaction.TransactionDateTime.Value,
                            financialGateway.GetBatchTimeOffset() );

                        var batchChanges = new List<string>();

                        if ( batch.Id == 0 )
                        {
                            batchChanges.Add( "Generated the batch" );
                            History.EvaluateChange( batchChanges, "Batch Name", string.Empty, batch.Name );
                            History.EvaluateChange( batchChanges, "Status", null, batch.Status );
                            History.EvaluateChange( batchChanges, "Start Date/Time", null, batch.BatchStartDateTime );
                            History.EvaluateChange( batchChanges, "End Date/Time", null, batch.BatchEndDateTime );
                        }

                        decimal newControlAmount = batch.ControlAmount + transaction.TotalAmount;
                        History.EvaluateChange( batchChanges, "Control Amount", batch.ControlAmount.FormatAsCurrency(), newControlAmount.FormatAsCurrency() );
                        batch.ControlAmount = newControlAmount;

                        transaction.BatchId = batch.Id;
                        batch.Transactions.Add( transaction );

                        rockContext.SaveChanges();

                        HistoryService.SaveChanges(
                            rockContext,
                            typeof( FinancialBatch ),
                            Rock.SystemGuid.Category.HISTORY_FINANCIAL_BATCH.AsGuid(),
                            batch.Id,
                            batchChanges
                        );

                        HistoryService.SaveChanges(
                            rockContext,
                            typeof( FinancialBatch ),
                            Rock.SystemGuid.Category.HISTORY_FINANCIAL_TRANSACTION.AsGuid(),
                            batch.Id,
                            txnChanges,
                            person.FullName,
                            typeof( FinancialTransaction ),
                            transaction.Id
                        );

                        SendReceipt( transaction.Id );

                        TransactionCode = transaction.TransactionCode;
                    }
                    else
                    {
                        return false;
                    }
                }

                tdTransactionCodeReceipt.Description = TransactionCode;
                tdTransactionCodeReceipt.Visible = !string.IsNullOrWhiteSpace( TransactionCode );

                tdScheduleId.Description = ScheduleId;
                tdScheduleId.Visible = !string.IsNullOrWhiteSpace( ScheduleId );

                tdNameReceipt.Description = paymentInfo.FullName;
                tdPhoneReceipt.Description = paymentInfo.Phone;
                tdEmailReceipt.Description = paymentInfo.Email;
                tdAddressReceipt.Description = string.Format( "{0} {1}, {2} {3}", paymentInfo.Street1, paymentInfo.City, paymentInfo.State, paymentInfo.PostalCode );

                rptAccountListReceipt.DataSource = SelectedAccounts.Where( a => a.Amount != 0 );
                rptAccountListReceipt.DataBind();

                tdTotalReceipt.Description = paymentInfo.Amount.ToString( "C" );

                tdPaymentMethodReceipt.Description = paymentInfo.CurrencyTypeValue.Description;
                tdAccountNumberReceipt.Description = paymentInfo.MaskedNumber;
                tdWhenReceipt.Description = schedule != null ? schedule.ToString() : "Today";

                // If there was a transaction code returned and this was not already created from a previous saved account,
                // show the option to save the account.
                if ( !( paymentInfo is ReferencePaymentInfo ) && !string.IsNullOrWhiteSpace( TransactionCode ) && gateway.SupportsSavedAccount( paymentInfo.CurrencyTypeValue ) )
                {
                    cbSaveAccount.Visible = true;
                    pnlSaveAccount.Visible = true;
                    txtSaveAccount.Visible = true;

                    // If current person does not have a login, have them create a username and password
                    phCreateLogin.Visible = !new UserLoginService( rockContext ).GetByPersonId( person.Id ).Any();
                }
                else
                {
                    pnlSaveAccount.Visible = false;
                }

                return true;
            }
            else
            {
                pnlDupWarning.Visible = true;
                divActions.Visible = false;
                errorMessage = string.Empty;
                return false;
            }
        }
        // helper functional methods (like BindGrid(), etc.)
        private void ShowContent()
        {
            // get scheduled contributions for current user
            if ( CurrentPerson != null )
            {
                var rockContext = new RockContext();
                var transactionService = new FinancialScheduledTransactionService( rockContext );
                var personService = new PersonService( rockContext );

                // get business giving id
                var givingIds = personService.GetBusinesses( CurrentPerson.Id ).Select( g => g.GivingId ).ToList();

                // add the person's regular giving id
                givingIds.Add( CurrentPerson.GivingId );

                var schedules = transactionService.Queryable( "ScheduledTransactionDetails.Account" )
                    .Where( s => givingIds.Contains( s.AuthorizedPersonAlias.Person.GivingId ) && s.IsActive == true );

                // filter the list if necesssary
                var gatewayFilterGuid = GetAttributeValue( "GatewayFilter" ).AsGuidOrNull();
                if ( gatewayFilterGuid != null )
                {
                    schedules = schedules.Where( s => s.FinancialGateway.Guid == gatewayFilterGuid );
                }

                rptScheduledTransactions.DataSource = schedules.ToList();
                rptScheduledTransactions.DataBind();

                if ( schedules.Count() == 0 )
                {
                    pnlNoScheduledTransactions.Visible = true;
                    lNoScheduledTransactionsMessage.Text = string.Format("No {0} currently exist.", GetAttributeValue("TransactionLabel").Pluralize().ToLower());
                }
            }
        }
Beispiel #14
0
        /// <summary>
        /// Processes the payments.
        /// </summary>
        /// <param name="gateway">The gateway.</param>
        /// <param name="batchNamePrefix">The batch name prefix.</param>
        /// <param name="payments">The payments.</param>
        /// <param name="batchUrlFormat">The batch URL format.</param>
        /// <param name="receiptEmail">The receipt email.</param>
        /// <returns></returns>
        public static string ProcessPayments(FinancialGateway gateway, string batchNamePrefix, List <Payment> payments, string batchUrlFormat = "", Guid?receiptEmail = null)
        {
            int totalPayments               = 0;
            int totalAlreadyDownloaded      = 0;
            int totalNoScheduledTransaction = 0;
            int totalAdded         = 0;
            int totalReversals     = 0;
            int totalStatusChanges = 0;

            var batches               = new List <FinancialBatch>();
            var batchSummary          = new Dictionary <Guid, List <Decimal> >();
            var initialControlAmounts = new Dictionary <Guid, decimal>();

            var txnPersonNames = new Dictionary <Guid, string>();

            var gatewayComponent = gateway.GetGatewayComponent();

            var newTransactions = new List <FinancialTransaction>();

            using (var rockContext = new RockContext())
            {
                var accountService      = new FinancialAccountService(rockContext);
                var txnService          = new FinancialTransactionService(rockContext);
                var batchService        = new FinancialBatchService(rockContext);
                var scheduledTxnService = new FinancialScheduledTransactionService(rockContext);

                var contributionTxnType = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.TRANSACTION_TYPE_CONTRIBUTION.AsGuid());

                var defaultAccount = accountService.Queryable()
                                     .Where(a =>
                                            a.IsActive &&
                                            !a.ParentAccountId.HasValue &&
                                            (!a.StartDate.HasValue || a.StartDate.Value <= RockDateTime.Now) &&
                                            (!a.EndDate.HasValue || a.EndDate.Value >= RockDateTime.Now)
                                            )
                                     .OrderBy(a => a.Order)
                                     .FirstOrDefault();

                var batchTxnChanges         = new Dictionary <Guid, List <string> >();
                var batchBatchChanges       = new Dictionary <Guid, List <string> >();
                var scheduledTransactionIds = new List <int>();

                foreach (var payment in payments.Where(p => p.Amount > 0.0M))
                {
                    totalPayments++;

                    var scheduledTransaction = scheduledTxnService.GetByScheduleId(payment.GatewayScheduleId);
                    if (scheduledTransaction != null)
                    {
                        // Find existing payments with same transaction code
                        var txns = txnService
                                   .Queryable("TransactionDetails")
                                   .Where(t => t.TransactionCode == payment.TransactionCode)
                                   .ToList();

                        // Calculate whether a transaction needs to be added
                        var txnAmount = CalculateTransactionAmount(payment, txns);
                        if (txnAmount != 0.0M)
                        {
                            scheduledTransactionIds.Add(scheduledTransaction.Id);
                            if (payment.ScheduleActive.HasValue)
                            {
                                scheduledTransaction.IsActive = payment.ScheduleActive.Value;
                            }

                            var transaction = new FinancialTransaction();
                            transaction.FinancialPaymentDetail = new FinancialPaymentDetail();

                            transaction.Guid                    = Guid.NewGuid();
                            transaction.TransactionCode         = payment.TransactionCode;
                            transaction.TransactionDateTime     = payment.TransactionDateTime;
                            transaction.Status                  = payment.Status;
                            transaction.StatusMessage           = payment.StatusMessage;
                            transaction.ScheduledTransactionId  = scheduledTransaction.Id;
                            transaction.AuthorizedPersonAliasId = scheduledTransaction.AuthorizedPersonAliasId;
                            transaction.SourceTypeValueId       = scheduledTransaction.SourceTypeValueId;
                            txnPersonNames.Add(transaction.Guid, scheduledTransaction.AuthorizedPersonAlias.Person.FullName);
                            transaction.FinancialGatewayId     = gateway.Id;
                            transaction.TransactionTypeValueId = contributionTxnType.Id;

                            if (txnAmount < 0.0M)
                            {
                                transaction.Summary = "Reversal for previous transaction that failed during processing." + Environment.NewLine;
                            }

                            var currencyTypeValue   = payment.CurrencyTypeValue;
                            var creditCardTypevalue = payment.CreditCardTypeValue;

                            if (scheduledTransaction.FinancialPaymentDetail != null)
                            {
                                if (currencyTypeValue == null && scheduledTransaction.FinancialPaymentDetail.CurrencyTypeValueId.HasValue)
                                {
                                    currencyTypeValue = DefinedValueCache.Read(scheduledTransaction.FinancialPaymentDetail.CurrencyTypeValueId.Value);
                                }

                                if (creditCardTypevalue == null && scheduledTransaction.FinancialPaymentDetail.CreditCardTypeValueId.HasValue)
                                {
                                    creditCardTypevalue = DefinedValueCache.Read(scheduledTransaction.FinancialPaymentDetail.CreditCardTypeValueId.Value);
                                }

                                transaction.FinancialPaymentDetail.AccountNumberMasked      = scheduledTransaction.FinancialPaymentDetail.AccountNumberMasked;
                                transaction.FinancialPaymentDetail.NameOnCardEncrypted      = scheduledTransaction.FinancialPaymentDetail.NameOnCardEncrypted;
                                transaction.FinancialPaymentDetail.ExpirationMonthEncrypted = scheduledTransaction.FinancialPaymentDetail.ExpirationMonthEncrypted;
                                transaction.FinancialPaymentDetail.ExpirationYearEncrypted  = scheduledTransaction.FinancialPaymentDetail.ExpirationYearEncrypted;
                                transaction.FinancialPaymentDetail.BillingLocationId        = scheduledTransaction.FinancialPaymentDetail.BillingLocationId;
                            }

                            if (currencyTypeValue != null)
                            {
                                transaction.FinancialPaymentDetail.CurrencyTypeValueId = currencyTypeValue.Id;
                            }
                            if (creditCardTypevalue != null)
                            {
                                transaction.FinancialPaymentDetail.CreditCardTypeValueId = creditCardTypevalue.Id;
                            }

                            // Try to allocate the amount of the transaction based on the current scheduled transaction accounts
                            decimal remainingAmount = Math.Abs(txnAmount);
                            foreach (var detail in scheduledTransaction.ScheduledTransactionDetails.Where(d => d.Amount != 0.0M))
                            {
                                var transactionDetail = new FinancialTransactionDetail();
                                transactionDetail.AccountId = detail.AccountId;

                                if (detail.Amount <= remainingAmount)
                                {
                                    // If the configured amount for this account is less than or equal to the remaining
                                    // amount, allocate the configured amount
                                    transactionDetail.Amount = detail.Amount;
                                    remainingAmount         -= detail.Amount;
                                }
                                else
                                {
                                    // If the configured amount is greater than the remaining amount, only allocate
                                    // the remaining amount
                                    transaction.Summary      += "Note: Downloaded transaction amount was less than the configured allocation amounts for the Scheduled Transaction.";
                                    transactionDetail.Amount  = remainingAmount;
                                    transactionDetail.Summary = "Note: The downloaded amount was not enough to apply the configured amount to this account.";
                                    remainingAmount           = 0.0M;
                                }

                                transaction.TransactionDetails.Add(transactionDetail);

                                if (remainingAmount <= 0.0M)
                                {
                                    // If there's no amount left, break out of details
                                    break;
                                }
                            }

                            // If there's still amount left after allocating based on current config, add the remainder
                            // to the account that was configured for the most amount
                            if (remainingAmount > 0.0M)
                            {
                                transaction.Summary += "Note: Downloaded transaction amount was greater than the configured allocation amounts for the Scheduled Transaction.";
                                var transactionDetail = transaction.TransactionDetails
                                                        .OrderByDescending(d => d.Amount)
                                                        .FirstOrDefault();
                                if (transactionDetail == null && defaultAccount != null)
                                {
                                    transactionDetail           = new FinancialTransactionDetail();
                                    transactionDetail.AccountId = defaultAccount.Id;
                                    transaction.TransactionDetails.Add(transactionDetail);
                                }
                                if (transactionDetail != null)
                                {
                                    transactionDetail.Amount += remainingAmount;
                                    transactionDetail.Summary = "Note: Extra amount was applied to this account.";
                                }
                            }

                            // If the amount to apply was negative, update all details to be negative (absolute value was used when allocating to accounts)
                            if (txnAmount < 0.0M)
                            {
                                foreach (var txnDetail in transaction.TransactionDetails)
                                {
                                    txnDetail.Amount = 0 - txnDetail.Amount;
                                }
                            }

                            // Get the batch
                            var batch = batchService.Get(
                                batchNamePrefix,
                                currencyTypeValue,
                                creditCardTypevalue,
                                transaction.TransactionDateTime.Value,
                                gateway.GetBatchTimeOffset(),
                                batches);

                            var batchChanges = new List <string>();
                            if (batch.Id != 0)
                            {
                                initialControlAmounts.AddOrIgnore(batch.Guid, batch.ControlAmount);
                            }
                            batch.ControlAmount += transaction.TotalAmount;

                            batch.Transactions.Add(transaction);

                            if (txnAmount > 0.0M && receiptEmail.HasValue)
                            {
                                newTransactions.Add(transaction);
                            }

                            // Add summary
                            if (!batchSummary.ContainsKey(batch.Guid))
                            {
                                batchSummary.Add(batch.Guid, new List <Decimal>());
                            }
                            batchSummary[batch.Guid].Add(txnAmount);

                            if (txnAmount > 0.0M)
                            {
                                totalAdded++;
                            }
                            else
                            {
                                totalReversals++;
                            }
                        }
                        else
                        {
                            totalAlreadyDownloaded++;

                            foreach (var txn in txns.Where(t => t.Status != payment.Status || t.StatusMessage != payment.StatusMessage))
                            {
                                txn.Status        = payment.Status;
                                txn.StatusMessage = payment.StatusMessage;
                                totalStatusChanges++;
                            }
                        }
                    }
                    else
                    {
                        totalNoScheduledTransaction++;
                    }
                }

                rockContext.SaveChanges();

                // Queue a transaction to update the status of all affected scheduled transactions
                var updatePaymentStatusTxn = new Rock.Transactions.UpdatePaymentStatusTransaction(gateway.Id, scheduledTransactionIds);
                Rock.Transactions.RockQueue.TransactionQueue.Enqueue(updatePaymentStatusTxn);

                if (receiptEmail.HasValue)
                {
                    // Queue a transaction to send receipts
                    var newTransactionIds      = newTransactions.Select(t => t.Id).ToList();
                    var sendPaymentReceiptsTxn = new Rock.Transactions.SendPaymentReceipts(receiptEmail.Value, newTransactionIds);
                    Rock.Transactions.RockQueue.TransactionQueue.Enqueue(sendPaymentReceiptsTxn);
                }
            }

            StringBuilder sb = new StringBuilder();

            sb.AppendFormat("<li>{0} {1} downloaded.</li>", totalPayments.ToString("N0"),
                            (totalPayments == 1 ? "payment" : "payments"));

            if (totalAlreadyDownloaded > 0)
            {
                sb.AppendFormat("<li>{0} {1} previously downloaded and {2} already been added.</li>", totalAlreadyDownloaded.ToString("N0"),
                                (totalAlreadyDownloaded == 1 ? "payment was" : "payments were"),
                                (totalAlreadyDownloaded == 1 ? "has" : "have"));
            }

            if (totalStatusChanges > 0)
            {
                sb.AppendFormat("<li>{0} {1} previously downloaded but had a change of status.</li>", totalStatusChanges.ToString("N0"),
                                (totalStatusChanges == 1 ? "payment was" : "payments were"));
            }

            if (totalNoScheduledTransaction > 0)
            {
                sb.AppendFormat("<li>{0} {1} could not be matched to an existing scheduled payment profile.</li>", totalNoScheduledTransaction.ToString("N0"),
                                (totalNoScheduledTransaction == 1 ? "payment" : "payments"));
            }

            sb.AppendFormat("<li>{0} {1} successfully added.</li>", totalAdded.ToString("N0"),
                            (totalAdded == 1 ? "payment was" : "payments were"));

            if (totalReversals > 0)
            {
                sb.AppendFormat("<li>{0} {1} added as a reversal to a previous transaction.</li>", totalReversals.ToString("N0"),
                                (totalReversals == 1 ? "payment was" : "payments were"));
            }

            foreach (var batchItem in batchSummary)
            {
                int items = batchItem.Value.Count;
                if (items > 0)
                {
                    var batch = batches
                                .Where(b => b.Guid.Equals(batchItem.Key))
                                .FirstOrDefault();

                    string batchName = string.Format("'{0} ({1})'", batch.Name, batch.BatchStartDateTime.Value.ToString("d"));
                    if (!string.IsNullOrWhiteSpace(batchUrlFormat))
                    {
                        batchName = string.Format("<a href='{0}'>{1}</a>", string.Format(batchUrlFormat, batch.Id), batchName);
                    }

                    decimal sum = batchItem.Value.Sum();

                    string summaryformat = items == 1 ?
                                           "<li>{0} transaction of {1} was added to the {2} batch.</li>" :
                                           "<li>{0} transactions totaling {1} were added to the {2} batch</li>";

                    sb.AppendFormat(summaryformat, items.ToString("N0"), sum.FormatAsCurrency(), batchName);
                }
            }

            return(sb.ToString());
        }
        // helper functional methods (like BindGrid(), etc.)
        private void ShowContent()
        {
            // get pledges for current user
            if ( CurrentPerson != null )
            {
                var rockContext = new RockContext();
                FinancialScheduledTransactionService transactionService = new FinancialScheduledTransactionService( rockContext );

                var schedules = transactionService.Queryable( "ScheduledTransactionDetails.Account" )
                                .Where( s => s.AuthorizedPersonId == CurrentPerson.Id && s.IsActive == true );

                List<Dictionary<string, object>> scheduleSummaries = new List<Dictionary<string, object>>();

                foreach ( FinancialScheduledTransaction schedule in schedules )
                {
                    decimal totalAmount = 0;

                    Dictionary<string, object> scheduleSummary = new Dictionary<string, object>();
                    scheduleSummary.Add("Id", schedule.Id);
                    scheduleSummary.Add("Guid", schedule.Guid);
                    scheduleSummary.Add("StartDate", schedule.StartDate);
                    scheduleSummary.Add("EndDate", schedule.EndDate);
                    scheduleSummary.Add("NextPaymentDate", schedule.NextPaymentDate);

                    if ( schedule.NextPaymentDate.HasValue )
                    {
                        scheduleSummary.Add( "DaysTillNextPayment", (schedule.NextPaymentDate.Value - DateTime.Now).Days );
                    }
                    else
                    {
                        scheduleSummary.Add( "DaysTillNextPayment", null );
                    }

                    DateTime? lastPaymentDate = schedule.Transactions.Max(t => t.TransactionDateTime);
                    scheduleSummary.Add("LastPaymentDate", lastPaymentDate);

                    if ( lastPaymentDate.HasValue )
                    {
                        scheduleSummary.Add("DaysSinceLastPayment",  (DateTime.Now - lastPaymentDate.Value).Days);
                    }
                    else
                    {
                        scheduleSummary.Add( "DaysSinceLastPayment", null );
                    }

                    scheduleSummary.Add("CurrencyType", schedule.CurrencyTypeValue.Value);
                    scheduleSummary.Add("CreditCardType", schedule.CreditCardTypeValue.Value);
                    scheduleSummary.Add("UrlEncryptedKey", schedule.UrlEncodedKey);
                    scheduleSummary.Add("Frequency",  schedule.TransactionFrequencyValue.Value);
                    scheduleSummary.Add("FrequencyDescription", schedule.TransactionFrequencyValue.Description);

                    List<Dictionary<string, object>> summaryDetails = new List<Dictionary<string,object>>();

                    foreach ( FinancialScheduledTransactionDetail detail in schedule.ScheduledTransactionDetails )
                    {
                        Dictionary<string, object> detailSummary = new Dictionary<string,object>();
                        detailSummary.Add("AccountId", detail.Id);
                        detailSummary.Add("AccountName", detail.Account.Name);
                        detailSummary.Add("Amount", detail.Amount);
                        detailSummary.Add("Summary", detail.Summary);

                        summaryDetails.Add( detailSummary );

                        totalAmount += detail.Amount;
                    }

                    scheduleSummary.Add("ScheduledAmount", totalAmount);
                    scheduleSummary.Add( "TransactionDetails", summaryDetails );

                    scheduleSummaries.Add( scheduleSummary );
                }

                // added linked pages to mergefields
                Dictionary<string, object> linkedPages = new Dictionary<string, object>();
                linkedPages.Add( "ManageScheduledTransactionsPage", LinkedPageUrl( "ManageScheduledTransactionsPage", null ));
                linkedPages.Add( "TransactionHistoryPage", LinkedPageUrl( "TransactionHistoryPage", null ) );
                linkedPages.Add( "TransactionEntryPage", LinkedPageUrl( "TransactionEntryPage", null ) );

                var scheduleValues = new Dictionary<string, object>();
                scheduleValues.Add( "ScheduledTransactions", scheduleSummaries.ToList() );
                scheduleValues.Add( "LinkedPages", linkedPages );
                scheduleValues.Add( "Person", CurrentPerson );

                string content = GetAttributeValue( "Template" ).ResolveMergeFields( scheduleValues );

                // show merge fields if needed
                if ( GetAttributeValue( "EnableDebug" ).AsBoolean() )
                {
                    string debugInfo = string.Format( @"

                        <pre>
            {0}
                        </pre>
                    ", scheduleValues.LiquidHelpText() );

                    content += debugInfo;
                }

                lContent.Text = content;
            }
        }
        // helper functional methods (like BindGrid(), etc.)
        private void ShowContent()
        {
            // get scheduled contributions for current user
            if ( CurrentPerson != null )
            {
                var rockContext = new RockContext();
                FinancialScheduledTransactionService transactionService = new FinancialScheduledTransactionService( rockContext );

                var schedules = transactionService.Queryable( "ScheduledTransactionDetails.Account" )
                                .Where( s => s.AuthorizedPersonAlias.Person.GivingId == CurrentPerson.GivingId && s.IsActive == true );

                rptScheduledTransactions.DataSource = schedules.ToList();
                rptScheduledTransactions.DataBind();

                if ( schedules.Count() == 0 )
                {
                    pnlNoScheduledTransactions.Visible = true;
                    lNoScheduledTransactionsMessage.Text = string.Format("No {0} currently exist.", GetAttributeValue("TransactionLabel").Pluralize().ToLower());
                }
            }
        }
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Execute( IJobExecutionContext context )
        {
            var rockContext = new RockContext();
            JobDataMap dataMap = context.JobDetail.JobDataMap;

            // Get the details for the email that we'll be sending out.
            Guid? systemEmailGuid = dataMap.GetString( "ExpiringCreditCardEmail" ).AsGuidOrNull();
            SystemEmailService emailService = new SystemEmailService( rockContext );
            SystemEmail systemEmail = null;

            if ( systemEmailGuid.HasValue )
            {
                systemEmail = emailService.Get( systemEmailGuid.Value );
            }

            // Fetch the configured Workflow once if one was set, we'll use it later.
            Guid? workflowGuid = dataMap.GetString( "Workflow" ).AsGuidOrNull();
            WorkflowType workflowType = null;
            var workflowTypeService = new WorkflowTypeService( rockContext );
            var workflowService = new WorkflowService( rockContext );

            if ( workflowGuid != null )
            {
                workflowType = workflowTypeService.Get( workflowGuid.Value );
            }

            var qry = new FinancialScheduledTransactionService( rockContext )
                .Queryable( "ScheduledTransactionDetails,FinancialPaymentDetail.CurrencyTypeValue,FinancialPaymentDetail.CreditCardTypeValue" )
                .Where( t => t.IsActive && t.FinancialPaymentDetail.ExpirationMonthEncrypted != null
                && ( t.EndDate == null || t.EndDate > DateTime.Now ) )
                .AsNoTracking();

            var appRoot = Rock.Web.Cache.GlobalAttributesCache.Read( rockContext ).GetValue( "PublicApplicationRoot" );

            // Get the current month and year
            DateTime now = DateTime.Now;
            int month = now.Month;
            int year = now.Year;
            int counter = 0;
            foreach ( var transaction in qry )
            {
                int expirationMonthDecrypted = Int32.Parse( Encryption.DecryptString( transaction.FinancialPaymentDetail.ExpirationMonthEncrypted ) );
                int expirationYearDecrypted = Int32.Parse( Encryption.DecryptString( transaction.FinancialPaymentDetail.ExpirationYearEncrypted ) );
                string acctNum = transaction.FinancialPaymentDetail.AccountNumberMasked.Substring( transaction.FinancialPaymentDetail.AccountNumberMasked.Length - 4 );

                int warningYear = expirationYearDecrypted;
                int warningMonth = expirationMonthDecrypted - 1;
                if ( warningMonth == 0 )
                {
                    warningYear -= 1;
                    warningMonth = 12;
                }

                string warningDate = warningMonth.ToString() + warningYear.ToString();
                string currentMonthString = month.ToString() + year.ToString();

                if ( warningDate == currentMonthString )
                {
                    // as per ISO7813 https://en.wikipedia.org/wiki/ISO/IEC_7813
                    var expirationDate = string.Format( "{0:D2}/{1:D2}", expirationMonthDecrypted, expirationYearDecrypted );

                    var recipients = new List<RecipientData>();
                    var mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields( null );
                    var person = transaction.AuthorizedPersonAlias.Person;
                    mergeFields.Add( "Person", person );
                    mergeFields.Add( "Card", acctNum );
                    mergeFields.Add( "Expiring", expirationDate );
                    recipients.Add( new RecipientData( person.Email, mergeFields ) );

                    Email.Send( systemEmail.Guid, recipients, appRoot );

                    // Start workflow for this person
                    if ( workflowType != null )
                    {
                        Dictionary<string, string> attributes = new Dictionary<string, string>();
                        attributes.Add( "Person", transaction.AuthorizedPersonAlias.Guid.ToString() );
                        attributes.Add( "Card", acctNum );
                        attributes.Add( "Expiring", expirationDate );
                        StartWorkflow( workflowService, workflowType, attributes, string.Format( "{0} (scheduled transaction Id: {1})", person.FullName, transaction.Id ) );
                    }

                    counter++;
                }
            }

            context.Result = string.Format( "{0} scheduled credit card transactions were examined with {1} notice(s) sent.", qry.Count(), counter );
        }
        // helper functional methods (like BindGrid(), etc.)
        private void ShowContent()
        {
            List<Dictionary<string, object>> scheduleSummaries = new List<Dictionary<string, object>>();

            // get scheduled transactions for current user
            if ( CurrentPerson != null )
            {
                var rockContext = new RockContext();
                FinancialScheduledTransactionService transactionService = new FinancialScheduledTransactionService( rockContext );

                var schedules = transactionService.Queryable( "ScheduledTransactionDetails.Account" )
                                .Where( s => s.AuthorizedPersonAlias.Person.GivingId == CurrentPerson.GivingId && s.IsActive == true );

                foreach ( FinancialScheduledTransaction schedule in schedules )
                {
                    string errorMsgs = string.Empty;
                    transactionService.GetStatus( schedule, out errorMsgs );

                    decimal totalAmount = 0;

                    Dictionary<string, object> scheduleSummary = new Dictionary<string, object>();
                    scheduleSummary.Add("Id", schedule.Id);
                    scheduleSummary.Add("Guid", schedule.Guid);
                    scheduleSummary.Add("StartDate", schedule.StartDate);
                    scheduleSummary.Add("EndDate", schedule.EndDate);
                    scheduleSummary.Add("NextPaymentDate", schedule.NextPaymentDate);

                    if ( schedule.NextPaymentDate.HasValue )
                    {
                        scheduleSummary.Add( "DaysTillNextPayment", (schedule.NextPaymentDate.Value - DateTime.Now).Days );
                    }
                    else
                    {
                        scheduleSummary.Add( "DaysTillNextPayment", null );
                    }

                    DateTime? lastPaymentDate = schedule.Transactions.Max(t => t.TransactionDateTime);
                    scheduleSummary.Add("LastPaymentDate", lastPaymentDate);

                    if ( lastPaymentDate.HasValue )
                    {
                        scheduleSummary.Add("DaysSinceLastPayment",  (DateTime.Now - lastPaymentDate.Value).Days);
                    }
                    else
                    {
                        scheduleSummary.Add( "DaysSinceLastPayment", null );
                    }

                    scheduleSummary.Add("CurrencyType", ( schedule.FinancialPaymentDetail != null && schedule.FinancialPaymentDetail.CurrencyTypeValue != null ) ? schedule.FinancialPaymentDetail.CurrencyTypeValue.Value : "" );
                    scheduleSummary.Add( "CreditCardType", ( schedule.FinancialPaymentDetail != null && schedule.FinancialPaymentDetail.CreditCardTypeValue != null) ? schedule.FinancialPaymentDetail.CreditCardTypeValue.Value : "" );
                    scheduleSummary.Add("UrlEncryptedKey", schedule.UrlEncodedKey);
                    scheduleSummary.Add("Frequency",  schedule.TransactionFrequencyValue.Value);
                    scheduleSummary.Add("FrequencyDescription", schedule.TransactionFrequencyValue.Description);

                    List<Dictionary<string, object>> summaryDetails = new List<Dictionary<string,object>>();

                    foreach ( FinancialScheduledTransactionDetail detail in schedule.ScheduledTransactionDetails )
                    {
                        Dictionary<string, object> detailSummary = new Dictionary<string,object>();
                        detailSummary.Add("AccountId", detail.Id);
                        detailSummary.Add("AccountName", detail.Account.Name);
                        detailSummary.Add("Amount", detail.Amount);
                        detailSummary.Add("Summary", detail.Summary);

                        summaryDetails.Add( detailSummary );

                        totalAmount += detail.Amount;
                    }

                    scheduleSummary.Add("ScheduledAmount", totalAmount);
                    scheduleSummary.Add( "TransactionDetails", summaryDetails );

                    scheduleSummaries.Add( scheduleSummary );
                }

                rockContext.SaveChanges();
            }

            // added linked pages to mergefields
            Dictionary<string, object> linkedPages = new Dictionary<string, object>();
            linkedPages.Add( "ManageScheduledTransactionsPage", LinkedPageRoute( "ManageScheduledTransactionsPage" ) );
            linkedPages.Add( "TransactionHistoryPage", LinkedPageRoute( "TransactionHistoryPage" ) );
            linkedPages.Add( "TransactionEntryPage", LinkedPageRoute( "TransactionEntryPage" ) );

            var scheduleValues = new Dictionary<string, object>();
            scheduleValues.Add( "ScheduledTransactions", scheduleSummaries.ToList() );
            scheduleValues.Add( "LinkedPages", linkedPages );
            // TODO: When support for "Person" is not supported anymore (should use "CurrentPerson" instead), remove this line
            scheduleValues.Add( "Person", CurrentPerson );
            scheduleValues.Add( "CurrentPerson", CurrentPerson );

            string content = GetAttributeValue( "Template" ).ResolveMergeFields( scheduleValues );

            // show merge fields if needed
            if ( GetAttributeValue( "EnableDebug" ).AsBoolean() && IsUserAuthorized( Authorization.EDIT ) )
            {
                // TODO: When support for "Person" is not supported anymore (should use "CurrentPerson" instead), remove this line
                scheduleValues.Remove( "Person" );
                content += scheduleValues.lavaDebugInfo();
            }

            lContent.Text = content;
        }
        /// <summary>
        /// Gets the scheduled transaction.
        /// </summary>
        /// <param name="refresh">if set to <c>true</c> [refresh].</param>
        /// <returns></returns>
        private FinancialScheduledTransaction GetScheduledTransaction( bool refresh = false )
        {
            Person targetPerson = null;
            using ( var rockContext = new RockContext() )
            {
                // If impersonation is allowed, and a valid person key was used, set the target to that person
                bool allowImpersonation = GetAttributeValue( "Impersonation" ).AsBoolean();
                if ( allowImpersonation )
                {
                    string personKey = PageParameter( "Person" );
                    if ( !string.IsNullOrWhiteSpace( personKey ) )
                    {
                        targetPerson = new PersonService( rockContext ).GetByUrlEncodedKey( personKey );
                    }
                }

                if ( targetPerson == null )
                {
                    targetPerson = CurrentPerson;
                }

                // Verify that transaction id is valid for selected person
                if ( targetPerson != null )
                {
                    int txnId = int.MinValue;
                    if ( int.TryParse( PageParameter( "ScheduledTransactionId" ), out txnId ) )
                    {
                        var personService = new PersonService( rockContext );

                        var validGivingIds = new List<string> { targetPerson.GivingId };
                        validGivingIds.AddRange( personService.GetBusinesses( targetPerson.Id ).Select( b => b.GivingId ) );

                        var service = new FinancialScheduledTransactionService( rockContext );
                        var scheduledTransaction = service
                            .Queryable( "AuthorizedPersonAlias.Person,ScheduledTransactionDetails,FinancialGateway,FinancialPaymentDetail.CurrencyTypeValue,FinancialPaymentDetail.CreditCardTypeValue" )
                            .Where( t =>
                                t.Id == txnId &&
                                t.AuthorizedPersonAlias != null &&
                                t.AuthorizedPersonAlias.Person != null &&
                                validGivingIds.Contains( t.AuthorizedPersonAlias.Person.GivingId ) )
                            .FirstOrDefault();

                        if ( scheduledTransaction != null )
                        {
                            if ( scheduledTransaction.AuthorizedPersonAlias != null )
                            {
                                TargetPersonId = scheduledTransaction.AuthorizedPersonAlias.PersonId;
                            }
                            ScheduledTransactionId = scheduledTransaction.Id;

                            if ( scheduledTransaction.FinancialGateway != null )
                            {
                                scheduledTransaction.FinancialGateway.LoadAttributes( rockContext );
                            }

                            if ( refresh )
                            {
                                string errorMessages = string.Empty;
                                service.GetStatus( scheduledTransaction, out errorMessages );
                                rockContext.SaveChanges();
                            }

                            return scheduledTransaction;
                        }
                    }
                }
            }

            return null;
        }
        /// <summary>
        /// Handles the Click event of the lbSaveAccount control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void lbSaveAccount_Click( object sender, EventArgs e )
        {
            var rockContext = new RockContext();

            if ( string.IsNullOrWhiteSpace( TransactionCode ) )
            {
                nbSaveAccount.Text = "Sorry, the account information cannot be saved as there's not a valid transaction code to reference";
                nbSaveAccount.Visible = true;
                return;
            }

            if ( phCreateLogin.Visible )
            {
                if ( string.IsNullOrWhiteSpace( txtUserName.Text ) || string.IsNullOrWhiteSpace( txtPassword.Text ) )
                {
                    nbSaveAccount.Title = "Missing Informaton";
                    nbSaveAccount.Text = "A username and password are required when saving an account";
                    nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                    nbSaveAccount.Visible = true;
                    return;
                }

                if ( new UserLoginService( rockContext ).GetByUserName( txtUserName.Text ) != null )
                {
                    nbSaveAccount.Title = "Invalid Username";
                    nbSaveAccount.Text = "The selected Username is already being used.  Please select a different Username";
                    nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                    nbSaveAccount.Visible = true;
                    return;
                }

                if ( txtPasswordConfirm.Text != txtPassword.Text )
                {
                    nbSaveAccount.Title = "Invalid Password";
                    nbSaveAccount.Text = "The password and password confirmation do not match";
                    nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                    nbSaveAccount.Visible = true;
                    return;
                }
            }

            if ( !string.IsNullOrWhiteSpace( txtSaveAccount.Text ) )
            {
                GatewayComponent gateway = hfPaymentTab.Value == "ACH" ? _achGateway : _ccGateway;
                var ccCurrencyType = DefinedValueCache.Read( new Guid( Rock.SystemGuid.DefinedValue.CURRENCY_TYPE_CREDIT_CARD ) );
                var achCurrencyType = DefinedValueCache.Read( new Guid( Rock.SystemGuid.DefinedValue.CURRENCY_TYPE_ACH ) );

                string errorMessage = string.Empty;

                PersonAlias authorizedPersonAlias = null;
                string referenceNumber = string.Empty;
                int? currencyTypeValueId = hfPaymentTab.Value == "ACH" ? achCurrencyType.Id : ccCurrencyType.Id;

                if ( string.IsNullOrWhiteSpace( ScheduleId ) )
                {
                    var transaction = new FinancialTransactionService( rockContext ).GetByTransactionCode( TransactionCode );
                    if ( transaction != null && transaction.AuthorizedPersonAlias != null )
                    {
                        authorizedPersonAlias = transaction.AuthorizedPersonAlias;
                        referenceNumber = gateway.GetReferenceNumber( transaction, out errorMessage );
                    }
                }
                else
                {
                    var scheduledTransaction = new FinancialScheduledTransactionService( rockContext ).GetByScheduleId( ScheduleId );
                    if ( scheduledTransaction != null  )
                    {
                        authorizedPersonAlias = scheduledTransaction.AuthorizedPersonAlias;
                        referenceNumber = gateway.GetReferenceNumber( scheduledTransaction, out errorMessage );
                    }
                }

                if ( authorizedPersonAlias != null && authorizedPersonAlias.Person != null )
                {
                    if ( phCreateLogin.Visible )
                    {
                        var user = UserLoginService.Create(
                            rockContext,
                            authorizedPersonAlias.Person,
                            Rock.Model.AuthenticationServiceType.Internal,
                            EntityTypeCache.Read( Rock.SystemGuid.EntityType.AUTHENTICATION_DATABASE.AsGuid() ).Id,
                            txtUserName.Text,
                            txtPassword.Text,
                            false );

                        var mergeObjects = GlobalAttributesCache.GetMergeFields( null );
                        mergeObjects.Add( "ConfirmAccountUrl", RootPath + "ConfirmAccount" );

                        var personDictionary = authorizedPersonAlias.Person.ToLiquid() as Dictionary<string, object>;
                        mergeObjects.Add( "Person", personDictionary );

                        mergeObjects.Add( "User", user );

                        var recipients = new List<Rock.Communication.RecipientData>();
                        recipients.Add( new Rock.Communication.RecipientData( authorizedPersonAlias.Person.Email, mergeObjects ) );

                        Rock.Communication.Email.Send( GetAttributeValue( "ConfirmAccountTemplate" ).AsGuid(), recipients, ResolveRockUrl( "~/" ), ResolveRockUrl( "~~/" ) );
                    }

                    var paymentInfo = GetPaymentInfo();

                    if ( errorMessage.Any() )
                    {
                        nbSaveAccount.Title = "Invalid Transaction";
                        nbSaveAccount.Text = "Sorry, the account information cannot be saved. " + errorMessage;
                        nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                        nbSaveAccount.Visible = true;
                    }
                    else
                    {
                        if ( authorizedPersonAlias != null )
                        {
                            var savedAccount = new FinancialPersonSavedAccount();
                            savedAccount.PersonAliasId = authorizedPersonAlias.Id;
                            savedAccount.ReferenceNumber = referenceNumber;
                            savedAccount.Name = txtSaveAccount.Text;
                            savedAccount.MaskedAccountNumber = paymentInfo.MaskedNumber;
                            savedAccount.TransactionCode = TransactionCode;
                            savedAccount.GatewayEntityTypeId = gateway.TypeId;
                            savedAccount.CurrencyTypeValueId = currencyTypeValueId;
                            savedAccount.CreditCardTypeValueId = CreditCardTypeValueId;

                            var savedAccountService = new FinancialPersonSavedAccountService( rockContext );
                            savedAccountService.Add( savedAccount );
                            rockContext.SaveChanges();

                            cbSaveAccount.Visible = false;
                            txtSaveAccount.Visible = false;
                            phCreateLogin.Visible = false;
                            divSaveActions.Visible = false;

                            nbSaveAccount.Title = "Success";
                            nbSaveAccount.Text = "The account has been saved for future use";
                            nbSaveAccount.NotificationBoxType = NotificationBoxType.Success;
                            nbSaveAccount.Visible = true;
                        }
                    }
                }
                else
                {
                    nbSaveAccount.Title = "Invalid Transaction";
                    nbSaveAccount.Text = "Sorry, the account information cannot be saved as there's not a valid transaction code to reference";
                    nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                    nbSaveAccount.Visible = true;
                }
            }
            else
            {
                nbSaveAccount.Title = "Missing Account Name";
                nbSaveAccount.Text = "Please enter a name to use for this account";
                nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                nbSaveAccount.Visible = true;
            }
        }
        /// <summary>
        /// Processes the confirmation.
        /// </summary>
        /// <param name="errorMessage">The error message.</param>
        /// <returns></returns>
        private bool ProcessConfirmation( out string errorMessage )
        {
            var rockContext = new RockContext();
            errorMessage = string.Empty;

            if ( string.IsNullOrWhiteSpace( TransactionCode ) )
            {
                if ( Gateway == null )
                {
                    errorMessage = "There was a problem creating the payment gateway information";
                    return false;
                }

                var personService = new PersonService( rockContext );
                var transactionService = new FinancialScheduledTransactionService( rockContext );
                var transactionDetailService = new FinancialScheduledTransactionDetailService( rockContext );

                FinancialScheduledTransaction scheduledTransaction = null;

                if ( ScheduledTransactionId.HasValue )
                {
                    scheduledTransaction = transactionService.Get( ScheduledTransactionId.Value );
                }

                if ( scheduledTransaction == null )
                {
                    errorMessage = "There was a problem getting the transaction information";
                    return false;
                }

                if ( scheduledTransaction.AuthorizedPerson == null )
                {
                    errorMessage = "There was a problem determining the person associated with the transaction";
                    return false;
                }

                var changeSummary = new StringBuilder();

                // Get the payment schedule
                scheduledTransaction.TransactionFrequencyValueId = btnFrequency.SelectedValueAsId().Value;
                changeSummary.Append( DefinedValueCache.Read( scheduledTransaction.TransactionFrequencyValueId, rockContext ) );

                if ( dtpStartDate.SelectedDate.HasValue && dtpStartDate.SelectedDate > RockDateTime.Today )
                {
                    scheduledTransaction.StartDate = dtpStartDate.SelectedDate.Value;
                    changeSummary.AppendFormat( " starting {0}", scheduledTransaction.StartDate.ToShortDateString() );
                }
                else
                {
                    scheduledTransaction.StartDate = DateTime.MinValue;
                }

                changeSummary.AppendLine();

                PaymentInfo paymentInfo = GetPaymentInfo( personService, scheduledTransaction );
                if ( paymentInfo == null )
                {
                    errorMessage = "There was a problem creating the payment information";
                    return false;
                }
                else
                {
                }

                // If transaction is not active, attempt to re-activate it first
                if ( !scheduledTransaction.IsActive )
                {
                    if ( !transactionService.Reactivate( scheduledTransaction, out errorMessage ) )
                    {
                        return false;
                    }
                }

                if ( Gateway.UpdateScheduledPayment( scheduledTransaction, paymentInfo, out errorMessage ) )
                {
                    if ( paymentInfo.CurrencyTypeValue != null )
                    {
                        changeSummary.Append( paymentInfo.CurrencyTypeValue.Value );
                        scheduledTransaction.CurrencyTypeValueId = paymentInfo.CurrencyTypeValue.Id;

                        DefinedValueCache creditCardTypeValue = paymentInfo.CreditCardTypeValue;
                        if ( creditCardTypeValue != null )
                        {
                            changeSummary.AppendFormat( " - {0}", creditCardTypeValue.Value );
                            scheduledTransaction.CreditCardTypeValueId = creditCardTypeValue.Id;
                        }
                        else
                        {
                            scheduledTransaction.CreditCardTypeValueId = null;
                        }
                        changeSummary.AppendFormat( " {0}", paymentInfo.MaskedNumber );
                        changeSummary.AppendLine();
                    }

                    var selectedAccountIds = SelectedAccounts
                        .Where( a => a.Amount > 0 )
                        .Select( a => a.Id ).ToList();

                    var deletedAccounts = scheduledTransaction.ScheduledTransactionDetails
                        .Where( a => !selectedAccountIds.Contains( a.AccountId ) ).ToList();

                    foreach ( var deletedAccount in deletedAccounts )
                    {
                        scheduledTransaction.ScheduledTransactionDetails.Remove( deletedAccount );
                        transactionDetailService.Delete( deletedAccount );
                    }

                    foreach ( var account in SelectedAccounts )
                    {
                        var detail = scheduledTransaction.ScheduledTransactionDetails
                            .Where( d => d.AccountId == account.Id ).FirstOrDefault();
                        if ( detail == null )
                        {
                            detail = new FinancialScheduledTransactionDetail();
                            detail.AccountId = account.Id;
                            scheduledTransaction.ScheduledTransactionDetails.Add( detail );
                        }

                        detail.Amount = account.Amount;

                        changeSummary.AppendFormat( "{0}: {1:C2}", account.Name, account.Amount );
                        changeSummary.AppendLine();
                    }

                    rockContext.SaveChanges();

                    // Add a note about the change
                    var noteTypeService = new NoteTypeService( rockContext );
                    var noteType = noteTypeService.Get( scheduledTransaction.TypeId, "Note" );

                    var noteService = new NoteService( rockContext );
                    var note = new Note();
                    note.NoteTypeId = noteType.Id;
                    note.EntityId = scheduledTransaction.Id;
                    note.Caption = "Updated Transaction";
                    note.Text = changeSummary.ToString();
                    noteService.Add( note );

                    rockContext.SaveChanges();

                    ScheduleId = scheduledTransaction.GatewayScheduleId;
                    TransactionCode = scheduledTransaction.TransactionCode;

                    if (transactionService.GetStatus( scheduledTransaction, out errorMessage ))
                    {
                        rockContext.SaveChanges();
                    }
                }
                else
                {
                    return false;
                }

                tdTransactionCode.Description = TransactionCode;
                tdTransactionCode.Visible = !string.IsNullOrWhiteSpace( TransactionCode );

                tdScheduleId.Description = ScheduleId;
                tdScheduleId.Visible = !string.IsNullOrWhiteSpace( ScheduleId );

                return true;
            }
            else
            {
                pnlDupWarning.Visible = true;
                return false;
            }
        }
        private FinancialScheduledTransaction GetScheduledTransaction(bool refresh = false)
        {
            Person targetPerson = null;

            // If impersonation is allowed, and a valid person key was used, set the target to that person
            bool allowImpersonation = false;
            if ( bool.TryParse( GetAttributeValue( "Impersonation" ), out allowImpersonation ) && allowImpersonation )
            {
                string personKey = PageParameter( "Person" );
                if ( !string.IsNullOrWhiteSpace( personKey ) )
                {
                    targetPerson = new PersonService().GetByUrlEncodedKey( personKey );
                }
            }
            if ( targetPerson == null )
            {
                targetPerson = CurrentPerson;
            }

            // Verify that transaction id is valid for selected person
            if ( targetPerson != null )
            {
                int txnId = int.MinValue;
                if ( int.TryParse( PageParameter( "Txn" ), out txnId ) )
                {
                    var service = new FinancialScheduledTransactionService();
                    var scheduledTransaction = service.Queryable( "ScheduledTransactionDetails,GatewayEntityType" )
                        .Where( t =>
                            t.Id == txnId &&
                            ( t.AuthorizedPersonId == targetPerson.Id || t.AuthorizedPerson.GivingGroupId == targetPerson.GivingGroupId ) )
                        .FirstOrDefault();

                    if (scheduledTransaction != null)
                    {
                        TargetPersonId = scheduledTransaction.AuthorizedPersonId;
                        ScheduledTransactionId = scheduledTransaction.Id;

                        if ( refresh )
                        {
                            string errorMessages = string.Empty;
                            service.UpdateStatus( scheduledTransaction, CurrentPersonId, out errorMessages );
                        }

                        return scheduledTransaction;
                    }
                }
            }

            return null;
        }
        /// <summary>
        /// Executes this instance.
        /// </summary>
        public void Execute()
        {
            using ( var rockContext = new RockContext() )
            {
                var gateway = new FinancialGatewayService( rockContext ).Get( GatewayId );
                if ( gateway != null )
                {
                    var gatewayComponent = gateway.GetGatewayComponent();
                    if ( gatewayComponent != null )
                    {
                        var scheduledTxnService = new FinancialScheduledTransactionService( rockContext );

                        foreach( var txnId in ScheduledTransactionIds )
                        {
                            var scheduledTxn = scheduledTxnService.Get( txnId );
                            if ( scheduledTxn != null )
                            {
                                string statusMsgs = string.Empty;
                                gatewayComponent.GetScheduledPaymentStatus( scheduledTxn, out statusMsgs );
                                rockContext.SaveChanges();
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Processes the confirmation.
        /// </summary>
        /// <param name="errorMessage">The error message.</param>
        /// <returns></returns>
        private bool ProcessConfirmation( out string errorMessage )
        {
            errorMessage = string.Empty;

            if ( string.IsNullOrWhiteSpace( TransactionCode ) )
            {
                if ( Gateway == null )
                {
                    errorMessage = "There was a problem creating the payment gateway information";
                    return false;
                }

                using ( new UnitOfWorkScope() )
                {
                    var personService = new PersonService();
                    var transactionService = new FinancialScheduledTransactionService();
                    var transactionDetailService = new FinancialScheduledTransactionDetailService();

                    FinancialScheduledTransaction scheduledTransaction = null;

                    if ( ScheduledTransactionId.HasValue )
                    {
                        scheduledTransaction = transactionService.Get( ScheduledTransactionId.Value );
                    }

                    if ( scheduledTransaction == null )
                    {
                        errorMessage = "There was a problem getting the transaction information";
                        return false;
                    }

                    if ( scheduledTransaction.AuthorizedPerson == null )
                    {
                        errorMessage = "There was a problem determining the person associated with the transaction";
                        return false;
                    }

                    // Get the payment schedule
                    scheduledTransaction.TransactionFrequencyValueId = btnFrequency.SelectedValueAsId().Value;
                    if ( dtpStartDate.SelectedDate.HasValue && dtpStartDate.SelectedDate > DateTime.Today )
                    {
                        scheduledTransaction.StartDate = dtpStartDate.SelectedDate.Value;
                    }
                    else
                    {
                        scheduledTransaction.StartDate = DateTime.MinValue;
                    }

                    PaymentInfo paymentInfo = GetPaymentInfo( personService, scheduledTransaction );
                    if ( paymentInfo == null )
                    {
                        errorMessage = "There was a problem creating the payment information";
                        return false;
                    }
                    else
                    {

                    }

                    if ( Gateway.UpdateScheduledPayment( scheduledTransaction, paymentInfo, out errorMessage ) )
                    {
                        var selectedAccountIds = SelectedAccounts
                            .Where( a => a.Amount > 0 )
                            .Select( a => a.Id ).ToList();


                        var deletedAccounts = scheduledTransaction.ScheduledTransactionDetails
                            .Where( a => !selectedAccountIds.Contains( a.AccountId ) ).ToList();
                        foreach ( var deletedAccount in deletedAccounts )
                        {
                            scheduledTransaction.ScheduledTransactionDetails.Remove( deletedAccount );
                            transactionDetailService.Delete( deletedAccount, CurrentPersonId );
                        }

                        foreach ( var account in SelectedAccounts )
                        {
                            var detail = scheduledTransaction.ScheduledTransactionDetails
                                .Where( d => d.AccountId == account.Id ).FirstOrDefault();
                            if ( detail == null )
                            {
                                detail = new FinancialScheduledTransactionDetail();
                                detail.AccountId = account.Id;
                                scheduledTransaction.ScheduledTransactionDetails.Add( detail );
                            }
                            detail.Amount = account.Amount;
                        }

                        transactionService.Save( scheduledTransaction, CurrentPersonId );

                        ScheduleId = scheduledTransaction.GatewayScheduleId;
                        TransactionCode = scheduledTransaction.TransactionCode;
                    }
                    else
                    {
                        return false;
                    }

                    tdTransactionCode.Description = TransactionCode;
                    tdTransactionCode.Visible = !string.IsNullOrWhiteSpace( TransactionCode );

                    tdScheduleId.Description = ScheduleId;
                    tdScheduleId.Visible = !string.IsNullOrWhiteSpace( ScheduleId );

                    return true;
                }
            }
            else
            {
                pnlDupWarning.Visible = true;
                return false;
            }
        }
Beispiel #25
0
        /// <summary>
        /// Processes the payments.
        /// </summary>
        /// <param name="gateway">The gateway.</param>
        /// <param name="batchNamePrefix">The batch name prefix.</param>
        /// <param name="payments">The payments.</param>
        /// <param name="batchUrlFormat">The batch URL format.</param>
        /// <returns></returns>
        public static string ProcessPayments(GatewayComponent gateway, string batchNamePrefix, List <Payment> payments, string batchUrlFormat = "")
        {
            int totalPayments               = 0;
            int totalAlreadyDownloaded      = 0;
            int totalNoScheduledTransaction = 0;
            int totalAdded = 0;

            var batches      = new List <FinancialBatch>();
            var batchSummary = new Dictionary <Guid, List <Payment> >();

            var rockContext         = new RockContext();
            var accountService      = new FinancialAccountService(rockContext);
            var txnService          = new FinancialTransactionService(rockContext);
            var batchService        = new FinancialBatchService(rockContext);
            var scheduledTxnService = new FinancialScheduledTransactionService(rockContext);

            var contributionTxnTypeId = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.TRANSACTION_TYPE_CONTRIBUTION.AsGuid()).Id;

            var defaultAccount = accountService.Queryable()
                                 .Where(a =>
                                        a.IsActive &&
                                        !a.ParentAccountId.HasValue &&
                                        (!a.StartDate.HasValue || a.StartDate.Value <= RockDateTime.Now) &&
                                        (!a.EndDate.HasValue || a.EndDate.Value >= RockDateTime.Now)
                                        )
                                 .OrderBy(a => a.Order)
                                 .FirstOrDefault();

            foreach (var payment in payments.Where(p => p.Amount > 0.0M))
            {
                totalPayments++;

                // Only consider transactions that have not already been added
                if (txnService.GetByTransactionCode(payment.TransactionCode) == null)
                {
                    var scheduledTransaction = scheduledTxnService.GetByScheduleId(payment.GatewayScheduleId);
                    if (scheduledTransaction != null)
                    {
                        scheduledTransaction.IsActive = payment.ScheduleActive;

                        var transaction = new FinancialTransaction();
                        transaction.TransactionCode         = payment.TransactionCode;
                        transaction.TransactionDateTime     = payment.TransactionDateTime;
                        transaction.ScheduledTransactionId  = scheduledTransaction.Id;
                        transaction.AuthorizedPersonAliasId = scheduledTransaction.AuthorizedPersonAliasId;
                        transaction.GatewayEntityTypeId     = gateway.TypeId;
                        transaction.TransactionTypeValueId  = contributionTxnTypeId;

                        var currencyTypeValue = payment.CurrencyTypeValue;
                        if (currencyTypeValue == null && scheduledTransaction.CurrencyTypeValueId.HasValue)
                        {
                            currencyTypeValue = DefinedValueCache.Read(scheduledTransaction.CurrencyTypeValueId.Value);
                        }
                        if (currencyTypeValue != null)
                        {
                            transaction.CurrencyTypeValueId = currencyTypeValue.Id;
                        }

                        var creditCardTypevalue = payment.CreditCardTypeValue;
                        if (creditCardTypevalue == null && scheduledTransaction.CreditCardTypeValueId.HasValue)
                        {
                            creditCardTypevalue = DefinedValueCache.Read(scheduledTransaction.CreditCardTypeValueId.Value);
                        }
                        if (creditCardTypevalue != null)
                        {
                            transaction.CreditCardTypeValueId = creditCardTypevalue.Id;
                        }

                        //transaction.SourceTypeValueId = DefinedValueCache.Read( sourceGuid ).Id;

                        // Try to allocate the amount of the transaction based on the current scheduled transaction accounts
                        decimal remainingAmount = payment.Amount;
                        foreach (var detail in scheduledTransaction.ScheduledTransactionDetails.Where(d => d.Amount != 0.0M))
                        {
                            var transactionDetail = new FinancialTransactionDetail();
                            transactionDetail.AccountId = detail.AccountId;

                            if (detail.Amount <= remainingAmount)
                            {
                                // If the configured amount for this account is less than or equal to the remaining
                                // amount, allocate the configured amount
                                transactionDetail.Amount = detail.Amount;
                                remainingAmount         -= detail.Amount;
                            }
                            else
                            {
                                // If the configured amount is greater than the remaining amount, only allocate
                                // the remaining amount
                                transaction.Summary = "Note: Downloaded transaction amount was less than the configured allocation amounts for the Scheduled Transaction.";
                                detail.Amount       = remainingAmount;
                                detail.Summary      = "Note: The downloaded amount was not enough to apply the configured amount to this account.";
                                remainingAmount     = 0.0M;
                            }

                            transaction.TransactionDetails.Add(transactionDetail);

                            if (remainingAmount <= 0.0M)
                            {
                                // If there's no amount left, break out of details
                                break;
                            }
                        }

                        // If there's still amount left after allocating based on current config, add the remainder
                        // to the account that was configured for the most amount
                        if (remainingAmount > 0.0M)
                        {
                            transaction.Summary = "Note: Downloaded transaction amount was greater than the configured allocation amounts for the Scheduled Transaction.";
                            var transactionDetail = transaction.TransactionDetails
                                                    .OrderByDescending(d => d.Amount)
                                                    .First();
                            if (transactionDetail == null && defaultAccount != null)
                            {
                                transactionDetail           = new FinancialTransactionDetail();
                                transactionDetail.AccountId = defaultAccount.Id;
                            }
                            if (transactionDetail != null)
                            {
                                transactionDetail.Amount += remainingAmount;
                                transactionDetail.Summary = "Note: Extra amount was applied to this account.";
                            }
                        }

                        // Get the batch
                        var batch = batchService.Get(
                            batchNamePrefix,
                            currencyTypeValue,
                            creditCardTypevalue,
                            transaction.TransactionDateTime.Value,
                            gateway.BatchTimeOffset,
                            batches);

                        batch.ControlAmount += transaction.TotalAmount;
                        batch.Transactions.Add(transaction);

                        // Add summary
                        if (!batchSummary.ContainsKey(batch.Guid))
                        {
                            batchSummary.Add(batch.Guid, new List <Payment>());
                        }
                        batchSummary[batch.Guid].Add(payment);

                        totalAdded++;
                    }
                    else
                    {
                        totalNoScheduledTransaction++;
                    }
                }
                else
                {
                    totalAlreadyDownloaded++;
                }
            }

            rockContext.SaveChanges();

            StringBuilder sb = new StringBuilder();

            sb.AppendFormat("<li>{0} {1} downloaded.</li>", totalPayments.ToString("N0"),
                            (totalPayments == 1 ? "payment" : "payments"));

            if (totalAlreadyDownloaded > 0)
            {
                sb.AppendFormat("<li>{0} {1} previously downloaded and {2} already been added.</li>", totalAlreadyDownloaded.ToString("N0"),
                                (totalAlreadyDownloaded == 1 ? "payment was" : "payments were"),
                                (totalAlreadyDownloaded == 1 ? "has" : "have"));
            }

            if (totalNoScheduledTransaction > 0)
            {
                sb.AppendFormat("<li>{0} {1} could not be matched to an existing scheduled payment profile.</li>", totalNoScheduledTransaction.ToString("N0"),
                                (totalNoScheduledTransaction == 1 ? "payment" : "payments"));
            }

            sb.AppendFormat("<li>{0} {1} successfully added.</li>", totalAdded.ToString("N0"),
                            (totalAdded == 1 ? "payment was" : "payments were"));

            foreach (var batchItem in batchSummary)
            {
                int items = batchItem.Value.Count;
                if (items > 0)
                {
                    var batch = batches
                                .Where(b => b.Guid.Equals(batchItem.Key))
                                .FirstOrDefault();

                    string batchName = string.Format("'{0} ({1})'", batch.Name, batch.BatchStartDateTime.Value.ToString("d"));
                    if (!string.IsNullOrWhiteSpace(batchUrlFormat))
                    {
                        batchName = string.Format("<a href='{0}'>{1}</a>", string.Format(batchUrlFormat, batch.Id), batchName);
                    }

                    decimal sum = batchItem.Value.Select(p => p.Amount).Sum();


                    string summaryformat = items == 1 ?
                                           "<li>{0} transaction of {1} was added to the {2} batch.</li>" :
                                           "<li>{0} transactions totaling {1} were added to the {2} batch</li>";

                    sb.AppendFormat(summaryformat, items.ToString("N0"), sum.ToString("C2"), batchName);
                }
            }

            return(sb.ToString());
        }
Beispiel #26
0
        /// <summary>
        /// Processes the confirmation.
        /// </summary>
        /// <param name="errorMessage">The error message.</param>
        /// <returns></returns>
        private bool ProcessConfirmation( out string errorMessage )
        {
            var rockContext = new RockContext();
            if ( string.IsNullOrWhiteSpace( TransactionCode ) )
            {
                var transactionGuid = hfTransactionGuid.Value.AsGuid();

                bool isACHTxn = hfPaymentTab.Value == "ACH";
                var financialGateway = isACHTxn ? _achGateway : _ccGateway;
                var gateway = isACHTxn ? _achGatewayComponent : _ccGatewayComponent;

                if ( gateway == null )
                {
                    errorMessage = "There was a problem creating the payment gateway information";
                    return false;
                }

                // only create/update the person if they are giving as a person. If they are giving as a Business, the person record already exists
                Person person = GetPerson( !phGiveAsOption.Visible || tglGiveAsOption.Checked );
                if ( person == null )
                {
                    errorMessage = "There was a problem creating the person information";
                    return false;
                }

                if ( !person.PrimaryAliasId.HasValue )
                {
                    errorMessage = "There was a problem creating the person's primary alias";
                    return false;
                }

                Person BusinessOrPerson = GetPersonOrBusiness( person );

                PaymentInfo paymentInfo = GetTxnPaymentInfo( BusinessOrPerson, out errorMessage );
                if ( paymentInfo == null )
                {
                    return false;
                }

                PaymentSchedule schedule = GetSchedule();
                FinancialPaymentDetail paymentDetail = null;
                if ( schedule != null )
                {
                    schedule.PersonId = person.Id;

                    var scheduledTransactionAlreadyExists = new FinancialScheduledTransactionService( rockContext ).Queryable().FirstOrDefault( a => a.Guid == transactionGuid );
                    if ( scheduledTransactionAlreadyExists != null )
                    {
                        // hopefully shouldn't happen, but just in case the scheduledtransaction already went thru, show the success screen
                        ShowSuccess( gateway, person, paymentInfo, schedule, scheduledTransactionAlreadyExists.FinancialPaymentDetail, rockContext );
                        return true;
                    }

                    var scheduledTransaction = gateway.AddScheduledPayment( financialGateway, schedule, paymentInfo, out errorMessage );
                    if ( scheduledTransaction == null )
                    {
                        return false;
                    }

                    // manually assign the Guid that we generated at the beginning of the transaction UI entry to help make duplicate scheduled transactions impossible
                    scheduledTransaction.Guid = transactionGuid;

                    SaveScheduledTransaction( financialGateway, gateway, BusinessOrPerson, paymentInfo, schedule, scheduledTransaction, rockContext );
                    paymentDetail = scheduledTransaction.FinancialPaymentDetail.Clone( false );
                }
                else
                {
                    var transactionAlreadyExists = new FinancialTransactionService( rockContext ).Queryable().FirstOrDefault( a => a.Guid == transactionGuid );
                    if ( transactionAlreadyExists != null )
                    {
                        // hopefully shouldn't happen, but just in case the transaction already went thru, show the success screen
                        ShowSuccess( gateway, person, paymentInfo, null, transactionAlreadyExists.FinancialPaymentDetail, rockContext );
                        return true;
                    }

                    var transaction = gateway.Charge( financialGateway, paymentInfo, out errorMessage );
                    if ( transaction == null )
                    {
                        return false;
                    }

                    // manually assign the Guid that we generated at the beginning of the transaction UI entry to help make duplicate transactions impossible
                    transaction.Guid = transactionGuid;

                    SaveTransaction( financialGateway, gateway, BusinessOrPerson, paymentInfo, transaction, rockContext );
                    paymentDetail = transaction.FinancialPaymentDetail.Clone( false );
                }

                ShowSuccess( gateway, person, paymentInfo, schedule, paymentDetail, rockContext );

                return true;
            }
            else
            {
                pnlDupWarning.Visible = true;
                errorMessage = string.Empty;
                return false;
            }
        }
Beispiel #27
0
        /// <summary>
        /// Gets the scheduled transaction.
        /// </summary>
        /// <returns></returns>
        private FinancialScheduledTransaction GetScheduledTransaction()
        {
            int? txnId = PageParameter( "ScheduledTransactionId" ).AsIntegerOrNull();
            if (txnId.HasValue)
            {
                var rockContext = new RockContext();
                var service = new FinancialScheduledTransactionService( rockContext );
                return service
                    .Queryable( "ScheduledTransactionDetails,FinancialGateway,FinancialPaymentDetail.CurrencyTypeValue,FinancialPaymentDetail.CreditCardTypeValue" )
                    .Where( t => t.Id == txnId.Value )
                    .FirstOrDefault();
            }

            return null;
        }
Beispiel #28
0
        /// <summary>
        /// Handles the Click event of the lbSaveAccount control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void lbSaveAccount_Click( object sender, EventArgs e )
        {
            if ( string.IsNullOrWhiteSpace( TransactionCode ) )
            {
                nbSaveAccount.Text = "Sorry, the account information cannot be saved as there's not a valid transaction code to reference";
                nbSaveAccount.Visible = true;
                return;
            }

            using ( var rockContext = new RockContext() )
            {
                if ( phCreateLogin.Visible )
                {
                    if ( string.IsNullOrWhiteSpace( txtUserName.Text ) || string.IsNullOrWhiteSpace( txtPassword.Text ) )
                    {
                        nbSaveAccount.Title = "Missing Informaton";
                        nbSaveAccount.Text = "A username and password are required when saving an account";
                        nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                        nbSaveAccount.Visible = true;
                        return;
                    }

                    if ( new UserLoginService( rockContext ).GetByUserName( txtUserName.Text ) != null )
                    {
                        nbSaveAccount.Title = "Invalid Username";
                        nbSaveAccount.Text = "The selected Username is already being used.  Please select a different Username";
                        nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                        nbSaveAccount.Visible = true;
                        return;
                    }

                    if ( !UserLoginService.IsPasswordValid( txtPassword.Text ) )
                    {
                        nbSaveAccount.Title = string.Empty;
                        nbSaveAccount.Text = UserLoginService.FriendlyPasswordRules();
                        nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                        nbSaveAccount.Visible = true;
                        return;
                    }

                    if ( txtPasswordConfirm.Text != txtPassword.Text )
                    {
                        nbSaveAccount.Title = "Invalid Password";
                        nbSaveAccount.Text = "The password and password confirmation do not match";
                        nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                        nbSaveAccount.Visible = true;
                        return;
                    }
                }

                if ( !string.IsNullOrWhiteSpace( txtSaveAccount.Text ) )
                {
                    bool isACHTxn = hfPaymentTab.Value == "ACH";
                    var financialGateway = isACHTxn ? _achGateway : _ccGateway;
                    var gateway = isACHTxn ? _achGatewayComponent : _ccGatewayComponent;

                    if ( gateway != null )
                    {
                        var ccCurrencyType = DefinedValueCache.Read( new Guid( Rock.SystemGuid.DefinedValue.CURRENCY_TYPE_CREDIT_CARD ) );
                        var achCurrencyType = DefinedValueCache.Read( new Guid( Rock.SystemGuid.DefinedValue.CURRENCY_TYPE_ACH ) );

                        string errorMessage = string.Empty;

                        var person = GetPerson( false );
                        string referenceNumber = string.Empty;
                        FinancialPaymentDetail paymentDetail = null;
                        int? currencyTypeValueId = isACHTxn ? achCurrencyType.Id : ccCurrencyType.Id;

                        if ( string.IsNullOrWhiteSpace( ScheduleId ) )
                        {
                            var transaction = new FinancialTransactionService( rockContext ).GetByTransactionCode( TransactionCode );
                            if ( transaction != null && transaction.AuthorizedPersonAlias != null )
                            {
                                if ( transaction.FinancialGateway != null )
                                {
                                    transaction.FinancialGateway.LoadAttributes( rockContext );
                                }
                                referenceNumber = gateway.GetReferenceNumber( transaction, out errorMessage );
                                paymentDetail = transaction.FinancialPaymentDetail;
                            }
                        }
                        else
                        {
                            var scheduledTransaction = new FinancialScheduledTransactionService( rockContext ).GetByScheduleId( ScheduleId );
                            if ( scheduledTransaction != null )
                            {
                                if ( scheduledTransaction.FinancialGateway != null )
                                {
                                    scheduledTransaction.FinancialGateway.LoadAttributes( rockContext );
                                }
                                referenceNumber = gateway.GetReferenceNumber( scheduledTransaction, out errorMessage );
                                paymentDetail = scheduledTransaction.FinancialPaymentDetail;
                            }
                        }

                        if ( person != null && paymentDetail != null )
                        {
                            if ( phCreateLogin.Visible )
                            {
                                var user = UserLoginService.Create(
                                    rockContext,
                                    person,
                                    Rock.Model.AuthenticationServiceType.Internal,
                                    EntityTypeCache.Read( Rock.SystemGuid.EntityType.AUTHENTICATION_DATABASE.AsGuid() ).Id,
                                    txtUserName.Text,
                                    txtPassword.Text,
                                    false );

                                var mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields( this.RockPage, this.CurrentPerson );
                                mergeFields.Add( "ConfirmAccountUrl", RootPath + "ConfirmAccount" );

                                var personDictionary = person.ToLiquid() as Dictionary<string, object>;
                                mergeFields.Add( "Person", personDictionary );

                                mergeFields.Add( "User", user );

                                var recipients = new List<Rock.Communication.RecipientData>();
                                recipients.Add( new Rock.Communication.RecipientData( person.Email, mergeFields ) );

                                Rock.Communication.Email.Send( GetAttributeValue( "ConfirmAccountTemplate" ).AsGuid(), recipients, ResolveRockUrl( "~/" ), ResolveRockUrl( "~~/" ), false );
                            }

                            if ( errorMessage.Any() )
                            {
                                nbSaveAccount.Title = "Invalid Transaction";
                                nbSaveAccount.Text = "Sorry, the account information cannot be saved. " + errorMessage;
                                nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                                nbSaveAccount.Visible = true;
                            }
                            else
                            {
                                var savedAccount = new FinancialPersonSavedAccount();
                                savedAccount.PersonAliasId = person.PrimaryAliasId;
                                savedAccount.ReferenceNumber = referenceNumber;
                                savedAccount.Name = txtSaveAccount.Text;
                                savedAccount.TransactionCode = TransactionCode;
                                savedAccount.FinancialGatewayId = financialGateway.Id;
                                savedAccount.FinancialPaymentDetail = new FinancialPaymentDetail();
                                savedAccount.FinancialPaymentDetail.AccountNumberMasked = paymentDetail.AccountNumberMasked;
                                savedAccount.FinancialPaymentDetail.CurrencyTypeValueId = paymentDetail.CurrencyTypeValueId;
                                savedAccount.FinancialPaymentDetail.CreditCardTypeValueId = paymentDetail.CreditCardTypeValueId;
                                savedAccount.FinancialPaymentDetail.NameOnCardEncrypted = paymentDetail.NameOnCardEncrypted;
                                savedAccount.FinancialPaymentDetail.ExpirationMonthEncrypted = paymentDetail.ExpirationMonthEncrypted;
                                savedAccount.FinancialPaymentDetail.ExpirationYearEncrypted = paymentDetail.ExpirationYearEncrypted;
                                savedAccount.FinancialPaymentDetail.BillingLocationId = paymentDetail.BillingLocationId;

                                var savedAccountService = new FinancialPersonSavedAccountService( rockContext );
                                savedAccountService.Add( savedAccount );
                                rockContext.SaveChanges();

                                cbSaveAccount.Visible = false;
                                txtSaveAccount.Visible = false;
                                phCreateLogin.Visible = false;
                                divSaveActions.Visible = false;

                                nbSaveAccount.Title = "Success";
                                nbSaveAccount.Text = "The account has been saved for future use";
                                nbSaveAccount.NotificationBoxType = NotificationBoxType.Success;
                                nbSaveAccount.Visible = true;
                            }
                        }
                        else
                        {
                            nbSaveAccount.Title = "Invalid Transaction";
                            nbSaveAccount.Text = "Sorry, the account information cannot be saved as there's not a valid transaction code to reference.";
                            nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                            nbSaveAccount.Visible = true;
                        }
                    }
                    else
                    {
                        nbSaveAccount.Title = "Invalid Gateway";
                        nbSaveAccount.Text = "Sorry, the financial gateway information for this type of transaction is not valid.";
                        nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                        nbSaveAccount.Visible = true;
                    }
                }
                else
                {
                    nbSaveAccount.Title = "Missing Account Name";
                    nbSaveAccount.Text = "Please enter a name to use for this account.";
                    nbSaveAccount.NotificationBoxType = NotificationBoxType.Danger;
                    nbSaveAccount.Visible = true;
                }
            }
        }
Beispiel #29
0
        /// <summary>
        /// Handles the Click event of the lbCancelSchedule control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void lbCancelSchedule_Click( object sender, EventArgs e )
        {
            int? txnId = PageParameter( "ScheduledTransactionId" ).AsIntegerOrNull();
            if ( txnId.HasValue )
            {
                using ( var rockContext = new RockContext() )
                {
                    var txnService = new FinancialScheduledTransactionService( rockContext );
                    var txn = txnService
                        .Queryable( "AuthorizedPersonAlias.Person,FinancialGateway" )
                        .FirstOrDefault( t => t.Id == txnId.Value );
                    if ( txn != null )
                    {
                        if ( txn.FinancialGateway != null )
                        {
                            txn.FinancialGateway.LoadAttributes( rockContext );
                        }

                        string errorMessage = string.Empty;
                        if ( txnService.Cancel( txn, out errorMessage ) )
                        {
                            txnService.GetStatus( txn, out errorMessage );
                            rockContext.SaveChanges();
                        }
                        else
                        {
                            ShowErrorMessage( errorMessage );
                        }

                        ShowView( txn );
                    }
                }
            }
        }
        /// <summary>
        /// Processes the payments.
        /// </summary>
        /// <param name="gateway">The gateway.</param>
        /// <param name="batchNamePrefix">The batch name prefix.</param>
        /// <param name="payments">The payments.</param>
        /// <param name="batchUrlFormat">The batch URL format.</param>
        /// <param name="receiptEmail">The receipt email.</param>
        /// <returns></returns>
        public static string ProcessPayments( FinancialGateway gateway, string batchNamePrefix, List<Payment> payments, string batchUrlFormat = "", Guid? receiptEmail = null )
        {
            int totalPayments = 0;
            int totalAlreadyDownloaded = 0;
            int totalNoScheduledTransaction = 0;
            int totalAdded = 0;
            int totalReversals = 0;
            int totalStatusChanges = 0;

            var batches = new List<FinancialBatch>();
            var batchSummary = new Dictionary<Guid, List<Decimal>>();
            var initialControlAmounts = new Dictionary<Guid, decimal>();

            var txnPersonNames = new Dictionary<Guid, string>();

            var gatewayComponent = gateway.GetGatewayComponent();

            var newTransactions = new List<FinancialTransaction>();

            using ( var rockContext = new RockContext() )
            {
                var accountService = new FinancialAccountService( rockContext );
                var txnService = new FinancialTransactionService( rockContext );
                var batchService = new FinancialBatchService( rockContext );
                var scheduledTxnService = new FinancialScheduledTransactionService( rockContext );

                var contributionTxnType = DefinedValueCache.Read( Rock.SystemGuid.DefinedValue.TRANSACTION_TYPE_CONTRIBUTION.AsGuid() );

                var defaultAccount = accountService.Queryable()
                    .Where( a =>
                        a.IsActive &&
                        !a.ParentAccountId.HasValue &&
                        ( !a.StartDate.HasValue || a.StartDate.Value <= RockDateTime.Now ) &&
                        ( !a.EndDate.HasValue || a.EndDate.Value >= RockDateTime.Now )
                        )
                    .OrderBy( a => a.Order )
                    .FirstOrDefault();

                var batchTxnChanges = new Dictionary<Guid, List<string>>();
                var batchBatchChanges = new Dictionary<Guid, List<string>>();
                var scheduledTransactionIds = new List<int>();

                foreach ( var payment in payments.Where( p => p.Amount > 0.0M ) )
                {
                    totalPayments++;

                    var scheduledTransaction = scheduledTxnService.GetByScheduleId( payment.GatewayScheduleId );
                    if ( scheduledTransaction != null )
                    {
                        // Find existing payments with same transaction code
                        var txns = txnService
                            .Queryable( "TransactionDetails" )
                            .Where( t => t.TransactionCode == payment.TransactionCode )
                            .ToList();

                        // Calculate whether a transaction needs to be added
                        var txnAmount = CalculateTransactionAmount( payment, txns );
                        if ( txnAmount != 0.0M )
                        {
                            scheduledTransactionIds.Add( scheduledTransaction.Id );
                            if ( payment.ScheduleActive.HasValue )
                            {
                                scheduledTransaction.IsActive = payment.ScheduleActive.Value;
                            }

                            var transaction = new FinancialTransaction();
                            transaction.FinancialPaymentDetail = new FinancialPaymentDetail();

                            transaction.Guid = Guid.NewGuid();
                            transaction.TransactionCode = payment.TransactionCode;
                            transaction.TransactionDateTime = payment.TransactionDateTime;
                            transaction.Status = payment.Status;
                            transaction.StatusMessage = payment.StatusMessage;
                            transaction.ScheduledTransactionId = scheduledTransaction.Id;
                            transaction.AuthorizedPersonAliasId = scheduledTransaction.AuthorizedPersonAliasId;
                            transaction.SourceTypeValueId = scheduledTransaction.SourceTypeValueId;
                            txnPersonNames.Add( transaction.Guid, scheduledTransaction.AuthorizedPersonAlias.Person.FullName );
                            transaction.FinancialGatewayId = gateway.Id;
                            transaction.TransactionTypeValueId = contributionTxnType.Id;

                            if ( txnAmount < 0.0M )
                            {
                                transaction.Summary = "Reversal for previous transaction that failed during processing." + Environment.NewLine;
                            }

                            var currencyTypeValue = payment.CurrencyTypeValue;
                            var creditCardTypevalue = payment.CreditCardTypeValue;

                            if ( scheduledTransaction.FinancialPaymentDetail != null )
                            {
                                if ( currencyTypeValue == null && scheduledTransaction.FinancialPaymentDetail.CurrencyTypeValueId.HasValue )
                                {
                                    currencyTypeValue = DefinedValueCache.Read( scheduledTransaction.FinancialPaymentDetail.CurrencyTypeValueId.Value );
                                }

                                if ( creditCardTypevalue == null && scheduledTransaction.FinancialPaymentDetail.CreditCardTypeValueId.HasValue )
                                {
                                    creditCardTypevalue = DefinedValueCache.Read( scheduledTransaction.FinancialPaymentDetail.CreditCardTypeValueId.Value );
                                }

                                transaction.FinancialPaymentDetail.AccountNumberMasked = scheduledTransaction.FinancialPaymentDetail.AccountNumberMasked;
                                transaction.FinancialPaymentDetail.NameOnCardEncrypted = scheduledTransaction.FinancialPaymentDetail.NameOnCardEncrypted;
                                transaction.FinancialPaymentDetail.ExpirationMonthEncrypted = scheduledTransaction.FinancialPaymentDetail.ExpirationMonthEncrypted;
                                transaction.FinancialPaymentDetail.ExpirationYearEncrypted = scheduledTransaction.FinancialPaymentDetail.ExpirationYearEncrypted;
                                transaction.FinancialPaymentDetail.BillingLocationId = scheduledTransaction.FinancialPaymentDetail.BillingLocationId;
                            }

                            if ( currencyTypeValue != null )
                            {
                                transaction.FinancialPaymentDetail.CurrencyTypeValueId = currencyTypeValue.Id;
                            }
                            if ( creditCardTypevalue != null )
                            {
                                transaction.FinancialPaymentDetail.CreditCardTypeValueId = creditCardTypevalue.Id;
                            }

                            // Try to allocate the amount of the transaction based on the current scheduled transaction accounts
                            decimal remainingAmount = Math.Abs( txnAmount );
                            foreach ( var detail in scheduledTransaction.ScheduledTransactionDetails.Where( d => d.Amount != 0.0M ) )
                            {
                                var transactionDetail = new FinancialTransactionDetail();
                                transactionDetail.AccountId = detail.AccountId;

                                if ( detail.Amount <= remainingAmount )
                                {
                                    // If the configured amount for this account is less than or equal to the remaining
                                    // amount, allocate the configured amount
                                    transactionDetail.Amount = detail.Amount;
                                    remainingAmount -= detail.Amount;
                                }
                                else
                                {
                                    // If the configured amount is greater than the remaining amount, only allocate
                                    // the remaining amount
                                    transaction.Summary += "Note: Downloaded transaction amount was less than the configured allocation amounts for the Scheduled Transaction.";
                                    transactionDetail.Amount = remainingAmount;
                                    transactionDetail.Summary = "Note: The downloaded amount was not enough to apply the configured amount to this account.";
                                    remainingAmount = 0.0M;
                                }

                                transaction.TransactionDetails.Add( transactionDetail );

                                if ( remainingAmount <= 0.0M )
                                {
                                    // If there's no amount left, break out of details
                                    break;
                                }
                            }

                            // If there's still amount left after allocating based on current config, add the remainder
                            // to the account that was configured for the most amount
                            if ( remainingAmount > 0.0M )
                            {
                                transaction.Summary += "Note: Downloaded transaction amount was greater than the configured allocation amounts for the Scheduled Transaction.";
                                var transactionDetail = transaction.TransactionDetails
                                    .OrderByDescending( d => d.Amount )
                                    .FirstOrDefault();
                                if ( transactionDetail == null && defaultAccount != null )
                                {
                                    transactionDetail = new FinancialTransactionDetail();
                                    transactionDetail.AccountId = defaultAccount.Id;
                                    transaction.TransactionDetails.Add( transactionDetail );
                                }
                                if ( transactionDetail != null )
                                {
                                    transactionDetail.Amount += remainingAmount;
                                    transactionDetail.Summary = "Note: Extra amount was applied to this account.";
                                }
                            }

                            // If the amount to apply was negative, update all details to be negative (absolute value was used when allocating to accounts)
                            if ( txnAmount < 0.0M )
                            {
                                foreach ( var txnDetail in transaction.TransactionDetails )
                                {
                                    txnDetail.Amount = 0 - txnDetail.Amount;
                                }
                            }

                            // Get the batch
                            var batch = batchService.Get(
                                batchNamePrefix,
                                currencyTypeValue,
                                creditCardTypevalue,
                                transaction.TransactionDateTime.Value,
                                gateway.GetBatchTimeOffset(),
                                batches );

                            var batchChanges = new List<string>();
                            if ( batch.Id != 0 )
                            {
                                initialControlAmounts.AddOrIgnore( batch.Guid, batch.ControlAmount );
                            }
                            batch.ControlAmount += transaction.TotalAmount;

                            batch.Transactions.Add( transaction );

                            if ( txnAmount > 0.0M && receiptEmail.HasValue )
                            {
                                newTransactions.Add( transaction );
                            }

                            // Add summary
                            if ( !batchSummary.ContainsKey( batch.Guid ) )
                            {
                                batchSummary.Add( batch.Guid, new List<Decimal>() );
                            }
                            batchSummary[batch.Guid].Add( txnAmount );

                            if ( txnAmount > 0.0M )
                            {
                                totalAdded++;
                            }
                            else
                            {
                                totalReversals++;
                            }
                        }
                        else
                        {
                            totalAlreadyDownloaded++;

                            foreach ( var txn in txns.Where( t => t.Status != payment.Status || t.StatusMessage != payment.StatusMessage ) )
                            {
                                txn.Status = payment.Status;
                                txn.StatusMessage = payment.StatusMessage;
                                totalStatusChanges++;
                            }
                        }
                    }
                    else
                    {
                        totalNoScheduledTransaction++;
                    }
                }

                rockContext.SaveChanges();

                // Queue a transaction to update the status of all affected scheduled transactions
                var updatePaymentStatusTxn = new Rock.Transactions.UpdatePaymentStatusTransaction( gateway.Id, scheduledTransactionIds );
                Rock.Transactions.RockQueue.TransactionQueue.Enqueue( updatePaymentStatusTxn );

                if ( receiptEmail.HasValue )
                {
                    // Queue a transaction to send receipts
                    var newTransactionIds = newTransactions.Select( t => t.Id ).ToList();
                    var sendPaymentReceiptsTxn = new Rock.Transactions.SendPaymentReceipts( receiptEmail.Value, newTransactionIds );
                    Rock.Transactions.RockQueue.TransactionQueue.Enqueue( sendPaymentReceiptsTxn );
                }
            }

            StringBuilder sb = new StringBuilder();
            sb.AppendFormat( "<li>{0} {1} downloaded.</li>", totalPayments.ToString( "N0" ),
                ( totalPayments == 1 ? "payment" : "payments" ) );

            if ( totalAlreadyDownloaded > 0 )
            {
                sb.AppendFormat( "<li>{0} {1} previously downloaded and {2} already been added.</li>", totalAlreadyDownloaded.ToString( "N0" ),
                    ( totalAlreadyDownloaded == 1 ? "payment was" : "payments were" ),
                    ( totalAlreadyDownloaded == 1 ? "has" : "have" ) );
            }

            if ( totalStatusChanges > 0 )
            {
                sb.AppendFormat( "<li>{0} {1} previously downloaded but had a change of status.</li>", totalStatusChanges.ToString( "N0" ),
                ( totalStatusChanges == 1 ? "payment was" : "payments were" ) );
            }

            if ( totalNoScheduledTransaction > 0 )
            {
                sb.AppendFormat( "<li>{0} {1} could not be matched to an existing scheduled payment profile.</li>", totalNoScheduledTransaction.ToString( "N0" ),
                    ( totalNoScheduledTransaction == 1 ? "payment" : "payments" ) );
            }

            sb.AppendFormat( "<li>{0} {1} successfully added.</li>", totalAdded.ToString( "N0" ),
                ( totalAdded == 1 ? "payment was" : "payments were" ) );

            if ( totalReversals > 0 )
            {
                sb.AppendFormat( "<li>{0} {1} added as a reversal to a previous transaction.</li>", totalReversals.ToString( "N0" ),
                    ( totalReversals == 1 ? "payment was" : "payments were" ) );
            }

            if ( totalStatusChanges > 0 )
            {
                sb.AppendFormat( "<li>{0} {1} previously downloaded but had a change of status.</li>", totalStatusChanges.ToString( "N0" ),
                    ( totalStatusChanges == 1 ? "payment was" : "payments were" ) );
            }

            foreach ( var batchItem in batchSummary )
            {
                int items = batchItem.Value.Count;
                if ( items > 0 )
                {
                    var batch = batches
                        .Where( b => b.Guid.Equals( batchItem.Key ) )
                        .FirstOrDefault();

                    string batchName = string.Format( "'{0} ({1})'", batch.Name, batch.BatchStartDateTime.Value.ToString( "d" ) );
                    if ( !string.IsNullOrWhiteSpace( batchUrlFormat ) )
                    {
                        batchName = string.Format( "<a href='{0}'>{1}</a>", string.Format( batchUrlFormat, batch.Id ), batchName );
                    }

                    decimal sum = batchItem.Value.Sum();

                    string summaryformat = items == 1 ?
                        "<li>{0} transaction of {1} was added to the {2} batch.</li>" :
                        "<li>{0} transactions totaling {1} were added to the {2} batch</li>";

                    sb.AppendFormat( summaryformat, items.ToString( "N0" ), sum.FormatAsCurrency(), batchName );
                }
            }

            return sb.ToString();
        }
        /// <summary>
        /// Binds the grid.
        /// </summary>
        private void BindGrid()
        {
            int? personId = null;
            int? givingGroupId = null;

            bool validRequest = false;

            if ( TargetPerson != null )
            {
                personId = TargetPerson.Id;
                givingGroupId = TargetPerson.GivingGroupId;
                validRequest = true;
            }
            else
            {
                int personEntityTypeId = EntityTypeCache.Read( "Rock.Model.Person" ).Id;
                if ( !ContextTypesRequired.Any( e => e.Id == personEntityTypeId ) )
                {
                    validRequest = true;
                }
            }

            if ( validRequest )
            {
                var rockContext = new RockContext();
                var qry = new FinancialScheduledTransactionService( rockContext )
                    .Queryable( "ScheduledTransactionDetails,FinancialPaymentDetail.CurrencyTypeValue,FinancialPaymentDetail.CreditCardTypeValue" )
                    .AsNoTracking();

                // Amount Range
                var nre = new NumberRangeEditor();
                nre.DelimitedValues = gfSettings.GetUserPreference( "Amount" );
                if ( nre.LowerValue.HasValue )
                {
                    qry = qry.Where( t => t.ScheduledTransactionDetails.Sum( d => d.Amount ) >= nre.LowerValue.Value );
                }

                if ( nre.UpperValue.HasValue )
                {
                    qry = qry.Where( t => t.ScheduledTransactionDetails.Sum( d => d.Amount ) <= nre.UpperValue.Value );
                }

                // Frequency
                int? frequencyTypeId = gfSettings.GetUserPreference( "Frequency" ).AsIntegerOrNull();
                if ( frequencyTypeId.HasValue )
                {
                    qry = qry.Where( t => t.TransactionFrequencyValueId == frequencyTypeId.Value );
                }

                // Date Range
                var drp = new DateRangePicker();
                drp.DelimitedValues = gfSettings.GetUserPreference( "Created" );
                if ( drp.LowerValue.HasValue )
                {
                    qry = qry.Where( t => t.CreatedDateTime >= drp.LowerValue.Value );
                }

                if ( drp.UpperValue.HasValue )
                {
                    DateTime upperDate = drp.UpperValue.Value.Date.AddDays( 1 );
                    qry = qry.Where( t => t.CreatedDateTime < upperDate );
                }

                // Account Id
                int accountId = int.MinValue;
                if ( int.TryParse( gfSettings.GetUserPreference( "Account" ), out accountId ) )
                {
                    qry = qry.Where( t => t.ScheduledTransactionDetails.Any( d => d.AccountId == accountId ) );
                }

                // Active only (no filter)
                if ( string.IsNullOrWhiteSpace( gfSettings.GetUserPreference( "Include Inactive" ) ) )
                {
                    qry = qry.Where( t => t.IsActive );
                }

                if ( givingGroupId.HasValue )
                {
                    //  Person contributes with family
                    qry = qry.Where( t => t.AuthorizedPersonAlias.Person.GivingGroupId == givingGroupId );
                }
                else if ( personId.HasValue )
                {
                    // Person contributes individually
                    qry = qry.Where( t => t.AuthorizedPersonAlias.PersonId == personId );
                }

                SortProperty sortProperty = gList.SortProperty;
                if ( sortProperty != null )
                {
                    if ( sortProperty.Property == "Amount" )
                    {
                        if ( sortProperty.Direction == SortDirection.Ascending )
                        {
                            qry = qry.OrderBy( t => t.ScheduledTransactionDetails.Sum( d => (decimal?)d.Amount ) ?? 0.00M );
                        }
                        else
                        {
                            qry = qry.OrderByDescending( t => t.ScheduledTransactionDetails.Sum( d => (decimal?)d.Amount ) ?? 0.0M );
                        }
                    }
                    else
                    {
                        qry = qry.Sort( sortProperty );
                    }
                }
                else
                {
                    qry = qry
                        .OrderBy( t => t.AuthorizedPersonAlias.Person.LastName )
                        .ThenBy( t => t.AuthorizedPersonAlias.Person.NickName )
                        .ThenByDescending( t => t.IsActive )
                        .ThenByDescending( t => t.StartDate );
                }

                gList.SetLinqDataSource<FinancialScheduledTransaction>( qry );
                gList.DataBind();
            }
        }