/// <summary>
        /// Increments the prayer count.
        /// </summary>
        /// <param name="prayerRequestId">The prayer request identifier.</param>
        private void IncrementPrayerCount(int prayerRequestId)
        {
            using (var rockContext = new RockContext())
            {
                var request = new PrayerRequestService(rockContext).Get(prayerRequestId);
                var count   = request.PrayerCount ?? 0;
                request.PrayerCount = (count + 1);
                rockContext.SaveChanges();

                Guid?workflowTypeGuid = GetAttributeValue("PrayerCountWorkflow").AsGuidOrNull();
                if (workflowTypeGuid.HasValue)
                {
                    var workflowType = WorkflowTypeCache.Get(workflowTypeGuid.Value);
                    if (workflowType != null && (workflowType.IsActive ?? true))
                    {
                        if (GetAttributeValue("ProcessWorkflowImmediately").AsBoolean())
                        {
                            try
                            {
                                var           workflow = Workflow.Activate(workflowType, request.Name);
                                List <string> workflowErrors;
                                new WorkflowService(rockContext).Process(workflow, request, out workflowErrors);
                            }
                            catch (Exception ex)
                            {
                                ExceptionLogService.LogException(ex, this.Context);
                            }
                        }
                        else
                        {
                            var workflowDetails = new List <LaunchWorkflowDetails>();
                            workflowDetails.Add(new LaunchWorkflowDetails(request));
                            var transaction = new Rock.Transactions.LaunchWorkflowsTransaction(workflowTypeGuid.Value, workflowDetails);
                            // NOTE: In v8, the initiator will always be null while using delayed start.
                            //transaction.InitiatorPersonAliasId = CurrentPersonAliasId; // available in v9
                            Rock.Transactions.RockQueue.TransactionQueue.Enqueue(transaction);
                        }
                    }
                }
            }
        }
示例#2
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>
        /// <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;
            int totalNoMatchingTransaction = 0;
            int totalAdded         = 0;
            int totalReversals     = 0;
            int totalFailures      = 0;
            int totalStatusChanges = 0;

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

            var gatewayComponent = gateway.GetGatewayComponent();

            var newTransactions = 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);

                    // Find existing payments with same transaction code
                    FinancialTransaction originalTxn = null;
                    var txns = financialTransactionService
                               .Queryable("TransactionDetails")
                               .Where(t =>
                                      t.FinancialGatewayId.HasValue &&
                                      t.FinancialGatewayId.Value == gateway.Id &&
                                      t.TransactionCode == payment.TransactionCode)
                               .ToList();
                    if (txns.Any())
                    {
                        originalTxn = txns.OrderBy(t => t.Id).First();
                    }

                    var 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 (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;

                                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 batch = new FinancialBatchService(rockContext).Get(
                                batchNamePrefix,
                                currencyTypeValue,
                                creditCardTypevalue,
                                transaction.TransactionDateTime.Value,
                                gateway.GetBatchTimeOffset());

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

                                transaction.BatchId = batch.Id;
                                financialTransactionService.Add(transaction);
                            }
                            else
                            {
                                batch.Transactions.Add(transaction);
                            }

                            batch.ControlAmount += transaction.TotalAmount;

                            batch.Transactions.Add(transaction);

                            if (receiptEmail.HasValue && txnAmount > 0.0M)
                            {
                                newTransactions.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
                        {
                            totalNoMatchingTransaction++;
                        }
                    }
                    else
                    {
                        totalAlreadyDownloaded++;
                    }

                    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 && newTransactions.Any())
            {
                // 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);
            }

            // 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 (totalNoMatchingTransaction > 0)
            {
                sb.AppendFormat("<li>{0} {1} could not be matched to an existing scheduled payment profile or a previous transaction.</li>", totalNoMatchingTransaction.ToString("N0"),
                                (totalNoMatchingTransaction == 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 (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());
        }