/// <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> /// <param name="verboseLogging">If <c>true</c> then additional details will be logged.</param> /// <returns></returns> public static string ProcessPayments(FinancialGateway gateway, string batchNamePrefix, List <Payment> payments, string batchUrlFormat, Guid?receiptEmail, Guid?failedPaymentEmail, Guid?failedPaymentWorkflowType, bool verboseLogging) { int totalPayments = 0; int totalAlreadyDownloaded = 0; // If there is a payment without a transaction, but has one of the following status, don't report it as a 'unmatched' transaction. // If they have one of these statuses, and can't be matched, the user probably closed the browser or walked away before completing the transaction. string[] ignorableUnMatchedStatuses = new string[2] { "in_progress", "abandoned" }; 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.NameOnCard = financialPaymentDetail.NameOnCard; transaction.FinancialPaymentDetail.ExpirationMonth = financialPaymentDetail.ExpirationMonth; transaction.FinancialPaymentDetail.ExpirationYear = financialPaymentDetail.ExpirationYear; 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; transactionDetail.FeeCoverageAmount = detail.FeeCoverageAmount; 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 { // If the payment can't be matched (and we aren't ignoring it due to its status), add it to the payment without a transactions that we'll report. if (!ignorableUnMatchedStatuses.Contains(payment.Status, System.StringComparer.OrdinalIgnoreCase)) { 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 UpdatePaymentStatusTransaction(gateway.Id, scheduledTransactionIds); 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 SendPaymentReceipts(receiptEmail.Value, newTransactionIds); 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 SendPaymentReceipts(failedPaymentEmail.Value, newTransactionIds); 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 LaunchWorkflowsTransaction(failedPaymentWorkflowType.Value, workflowDetails); 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()) { if (verboseLogging) { 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>"); } else { sb.Append($"<li>{scheduledPaymentList.Count.ToString( "N0" )} gateway payments could not be matched to an existing scheduled payment profile.</li>"); } } var previousTransactionList = paymentsWithoutTransaction.Where(a => a.GatewayScheduleId.IsNullOrWhiteSpace()).Select(a => a.TransactionCode).ToList(); if (previousTransactionList.Any()) { if (verboseLogging) { 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>"); } else { sb.Append($"<li>{previousTransactionList.Count.ToString( "N0" )} gateway payments could not be matched to a previous transaction.</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 lbSave 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 lbSave_Click( object sender, EventArgs e ) { var rockContext = new RockContext(); var txnService = new FinancialTransactionService( rockContext ); var txnDetailService = new FinancialTransactionDetailService( rockContext ); var txnImageService = new FinancialTransactionImageService( rockContext ); var binaryFileService = new BinaryFileService( rockContext ); FinancialTransaction txn = null; int? txnId = hfTransactionId.Value.AsIntegerOrNull(); int? batchId = hfBatchId.Value.AsIntegerOrNull(); if ( txnId.HasValue ) { txn = txnService.Get( txnId.Value ); } if ( txn == null ) { txn = new FinancialTransaction(); txnService.Add( txn ); txn.BatchId = batchId; } if ( txn != null ) { if ( ppAuthorizedPerson.PersonId.HasValue ) { txn.AuthorizedPersonAliasId = ppAuthorizedPerson.PersonAliasId; } txn.TransactionDateTime = dtTransactionDateTime.SelectedDateTime; txn.TransactionTypeValueId = ddlTransactionType.SelectedValue.AsInteger(); txn.SourceTypeValueId = ddlSourceType.SelectedValueAsInt(); Guid? gatewayGuid = cpPaymentGateway.SelectedValueAsGuid(); if ( gatewayGuid.HasValue ) { var gatewayEntity = EntityTypeCache.Read( gatewayGuid.Value ); if ( gatewayEntity != null ) { txn.GatewayEntityTypeId = gatewayEntity.Id; } else { txn.GatewayEntityTypeId = null; } } else { txn.GatewayEntityTypeId = null; } txn.TransactionCode = tbTransactionCode.Text; txn.CurrencyTypeValueId = ddlCurrencyType.SelectedValueAsInt(); txn.CreditCardTypeValueId = ddlCreditCardType.SelectedValueAsInt(); txn.Summary = tbSummary.Text; if ( !Page.IsValid || !txn.IsValid ) { return; } foreach ( var txnDetail in TransactionDetailsState ) { if ( !txnDetail.IsValid ) { return; } } rockContext.WrapTransaction( () => { // Save the transaction rockContext.SaveChanges(); // Delete any transaction details that were removed var txnDetailsInDB = txnDetailService.Queryable().Where( a => a.TransactionId.Equals( txn.Id ) ).ToList(); var deletedDetails = from txnDetail in txnDetailsInDB where !TransactionDetailsState.Select( d => d.Guid ).Contains( txnDetail.Guid ) select txnDetail; deletedDetails.ToList().ForEach( txnDetail => { txnDetailService.Delete( txnDetail ); } ); rockContext.SaveChanges(); // Save Transaction Details foreach ( var editorTxnDetail in TransactionDetailsState ) { // Add or Update the activity type var txnDetail = txn.TransactionDetails.FirstOrDefault( d => d.Guid.Equals( editorTxnDetail.Guid ) ); if ( txnDetail == null ) { txnDetail = new FinancialTransactionDetail(); txnDetail.Guid = editorTxnDetail.Guid; txn.TransactionDetails.Add( txnDetail ); } txnDetail.AccountId = editorTxnDetail.AccountId; txnDetail.Amount = UseSimpleAccountMode ? tbSingleAccountAmount.Text.AsDecimal() : editorTxnDetail.Amount; txnDetail.Summary = editorTxnDetail.Summary; } rockContext.SaveChanges(); // Delete any transaction images that were removed var orphanedBinaryFileIds = new List<int>(); var txnImagesInDB = txnImageService.Queryable().Where( a => a.TransactionId.Equals( txn.Id ) ).ToList(); foreach ( var txnImage in txnImagesInDB.Where( i => !TransactionImagesState.Contains( i.BinaryFileId ) ) ) { orphanedBinaryFileIds.Add( txnImage.BinaryFileId ); txnImageService.Delete( txnImage ); } // Save Transaction Images int imageOrder = 0; foreach ( var binaryFileId in TransactionImagesState ) { // Add or Update the activity type var txnImage = txnImagesInDB.FirstOrDefault( i => i.BinaryFileId == binaryFileId ); if ( txnImage == null ) { txnImage = new FinancialTransactionImage(); txnImage.TransactionId = txn.Id; txn.Images.Add( txnImage ); } txnImage.BinaryFileId = binaryFileId; txnImage.Order = imageOrder; imageOrder++; } rockContext.SaveChanges(); // Make sure updated binary files are not temporary foreach ( var binaryFile in binaryFileService.Queryable().Where( f => TransactionImagesState.Contains( f.Id ) ) ) { binaryFile.IsTemporary = false; } // Delete any orphaned images foreach ( var binaryFile in binaryFileService.Queryable().Where( f => orphanedBinaryFileIds.Contains( f.Id ) ) ) { binaryFileService.Delete( binaryFile ); } rockContext.SaveChanges(); } ); // Save selected options to session state in order to prefill values for next added txn Session["NewTxnDefault_BatchId"] = txn.BatchId; Session["NewTxnDefault_TransactionDateTime"] = txn.TransactionDateTime; Session["NewTxnDefault_TransactionType"] = txn.TransactionTypeValueId; Session["NewTxnDefault_SourceType"] = txn.SourceTypeValueId; Session["NewTxnDefault_CurrencyType"] = txn.CurrencyTypeValueId; Session["NewTxnDefault_CreditCardType"] = txn.CreditCardTypeValueId; if ( TransactionDetailsState.Count() == 1 ) { Session["NewTxnDefault_Account"] = TransactionDetailsState.First().AccountId; } else { Session.Remove("NewTxnDefault_Account"); } // Requery the batch to support EF navigation properties var savedTxn = GetTransaction( txn.Id ); ShowReadOnlyDetails( savedTxn ); } }
/// <summary> /// Handles the Click event of the lbSave 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 lbSave_Click( object sender, EventArgs e ) { var rockContext = new RockContext(); var txnService = new FinancialTransactionService( rockContext ); var txnDetailService = new FinancialTransactionDetailService( rockContext ); var txnImageService = new FinancialTransactionImageService( rockContext ); var binaryFileService = new BinaryFileService( rockContext ); FinancialTransaction txn = null; int? txnId = hfTransactionId.Value.AsIntegerOrNull(); int? batchId = hfBatchId.Value.AsIntegerOrNull(); if ( txnId.HasValue ) { txn = txnService.Get( txnId.Value ); } if ( txn == null && batchId.HasValue ) { txn = new FinancialTransaction(); txnService.Add( txn ); txn.BatchId = batchId.Value; } if ( txn != null ) { txn.AuthorizedPersonId = ppAuthorizedPerson.PersonId; txn.TransactionDateTime = dtTransactionDateTime.SelectedDateTime; txn.TransactionTypeValueId = ddlTransactionType.SelectedValue.AsInteger(); txn.SourceTypeValueId = ddlSourceType.SelectedValueAsInt(); Guid? gatewayGuid = cpPaymentGateway.SelectedValueAsGuid(); if ( gatewayGuid.HasValue ) { var gatewayEntity = EntityTypeCache.Read( gatewayGuid.Value ); if ( gatewayEntity != null ) { txn.GatewayEntityTypeId = gatewayEntity.Id; } else { txn.GatewayEntityTypeId = null; } } else { txn.GatewayEntityTypeId = null; } txn.TransactionCode = tbTransactionCode.Text; txn.CurrencyTypeValueId = ddlCurrencyType.SelectedValueAsInt(); txn.CreditCardTypeValueId = ddlCreditCardType.SelectedValueAsInt(); txn.Summary = tbSummary.Text; if ( !Page.IsValid || !txn.IsValid ) { return; } foreach ( var txnDetail in TransactionDetailsState ) { if ( !txnDetail.IsValid ) { return; } } foreach ( var txnImage in TransactionImagesState ) { if ( !txnImage.IsValid ) { return; } } rockContext.WrapTransaction( () => { // Save the transaction rockContext.SaveChanges(); // Delete any transaction details that were removed var txnDetailsInDB = txnDetailService.Queryable().Where( a => a.TransactionId.Equals( txn.Id ) ).ToList(); var deletedDetails = from txnDetail in txnDetailsInDB where !TransactionDetailsState.Select( d => d.Guid ).Contains( txnDetail.Guid ) select txnDetail; deletedDetails.ToList().ForEach( txnDetail => { txnDetailService.Delete( txnDetail ); } ); rockContext.SaveChanges(); // Save Transaction Details foreach ( var editorTxnDetail in TransactionDetailsState ) { // Add or Update the activity type var txnDetail = txn.TransactionDetails.FirstOrDefault( d => d.Guid.Equals( editorTxnDetail.Guid ) ); if ( txnDetail == null ) { txnDetail = new FinancialTransactionDetail(); txnDetail.Guid = editorTxnDetail.Guid; txn.TransactionDetails.Add( txnDetail ); } txnDetail.AccountId = editorTxnDetail.AccountId; txnDetail.Amount = editorTxnDetail.Amount; txnDetail.Summary = editorTxnDetail.Summary; } rockContext.SaveChanges(); // Remove any images that do not have a binary file foreach ( var txnImage in TransactionImagesState.Where( i => i.BinaryFileId == 0 ).ToList() ) { TransactionImagesState.Remove( txnImage ); } // Delete any transaction images that were removed var txnImagesInDB = txnImageService.Queryable().Where( a => a.TransactionId.Equals( txn.Id ) ).ToList(); var deletedImages = from txnImage in txnImagesInDB where !TransactionImagesState.Select( d => d.Guid ).Contains( txnImage.Guid ) select txnImage; deletedImages.ToList().ForEach( txnImage => { txnImageService.Delete( txnImage ); } ); rockContext.SaveChanges(); // Save Transaction Images foreach ( var editorTxnImage in TransactionImagesState ) { // Add or Update the activity type var txnImage = txn.Images.FirstOrDefault( d => d.Guid.Equals( editorTxnImage.Guid ) ); if ( txnImage == null ) { txnImage = new FinancialTransactionImage(); txnImage.Guid = editorTxnImage.Guid; txn.Images.Add( txnImage ); } txnImage.BinaryFileId = editorTxnImage.BinaryFileId; txnImage.TransactionImageTypeValueId = editorTxnImage.TransactionImageTypeValueId; } rockContext.SaveChanges(); // Make sure updated binary files are not temporary var savedBinaryFileIds = txn.Images.Select( i => i.BinaryFileId ).ToList(); foreach ( var binaryFile in binaryFileService.Queryable().Where( f => savedBinaryFileIds.Contains( f.Id ) ) ) { binaryFile.IsTemporary = false; } // Delete any orphaned images var orphanedBinaryFileIds = BinaryFileIds.Where( f => !savedBinaryFileIds.Contains( f ) ); foreach ( var binaryFile in binaryFileService.Queryable().Where( f => orphanedBinaryFileIds.Contains( f.Id ) ) ) { binaryFileService.Delete( binaryFile ); } rockContext.SaveChanges(); } ); // Requery the batch to support EF navigation properties var savedTxn = GetTransaction( txn.Id ); ShowReadOnlyDetails( savedTxn ); } }
/// <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; } }
/// <summary> /// Handles the Click event of the lbSave 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 lbSave_Click( object sender, EventArgs e ) { var rockContext = new RockContext(); var txnService = new FinancialTransactionService( rockContext ); var txnDetailService = new FinancialTransactionDetailService( rockContext ); var txnImageService = new FinancialTransactionImageService( rockContext ); var binaryFileService = new BinaryFileService( rockContext ); FinancialTransaction txn = null; int? txnId = hfTransactionId.Value.AsIntegerOrNull(); int? batchId = hfBatchId.Value.AsIntegerOrNull(); var changes = new List<string>(); if ( txnId.HasValue ) { txn = txnService.Get( txnId.Value ); } if ( txn == null ) { txn = new FinancialTransaction(); txnService.Add( txn ); txn.BatchId = batchId; changes.Add( "Created transaction" ); } if ( txn != null ) { if ( txn.FinancialPaymentDetail == null ) { txn.FinancialPaymentDetail = new FinancialPaymentDetail(); } string newPerson = ppAuthorizedPerson.PersonName; if ( batchId.HasValue ) { if ( !txn.AuthorizedPersonAliasId.Equals( ppAuthorizedPerson.PersonAliasId ) ) { string prevPerson = ( txn.AuthorizedPersonAlias != null && txn.AuthorizedPersonAlias.Person != null ) ? txn.AuthorizedPersonAlias.Person.FullName : string.Empty; History.EvaluateChange( changes, "Person", prevPerson, newPerson ); } History.EvaluateChange( changes, "Date/Time", txn.TransactionDateTime, dtTransactionDateTime.SelectedDateTime ); History.EvaluateChange( changes, "Type", GetDefinedValue( txn.TransactionTypeValueId ), GetDefinedValue( ddlTransactionType.SelectedValue.AsInteger() ) ); History.EvaluateChange( changes, "Source", GetDefinedValue( txn.SourceTypeValueId ), GetDefinedValue( ddlSourceType.SelectedValueAsInt() ) ); if ( !txn.FinancialGatewayId.Equals( gpPaymentGateway.SelectedValueAsInt() ) ) { History.EvaluateChange( changes, "Gateway", GetFinancialGatewayName( txn.FinancialGatewayId, rockContext ), GetFinancialGatewayName( gpPaymentGateway.SelectedValueAsInt(), rockContext ) ); } History.EvaluateChange( changes, "Transaction Code", txn.TransactionCode, tbTransactionCode.Text ); History.EvaluateChange( changes, "Currency Type", GetDefinedValue( txn.FinancialPaymentDetail.CurrencyTypeValueId ), GetDefinedValue( ddlCurrencyType.SelectedValueAsInt() ) ); History.EvaluateChange( changes, "Credit Card Type", GetDefinedValue( txn.FinancialPaymentDetail.CreditCardTypeValueId ), GetDefinedValue( ddlCreditCardType.SelectedValueAsInt() ) ); History.EvaluateChange( changes, "Summary", txn.Summary, tbSummary.Text ); History.EvaluateChange( changes, "Is Refund", ( txn.RefundDetails != null ), cbIsRefund.Checked ); } txn.AuthorizedPersonAliasId = ppAuthorizedPerson.PersonAliasId; txn.TransactionDateTime = dtTransactionDateTime.SelectedDateTime; txn.TransactionTypeValueId = ddlTransactionType.SelectedValue.AsInteger(); txn.SourceTypeValueId = ddlSourceType.SelectedValueAsInt(); txn.FinancialGatewayId = gpPaymentGateway.SelectedValueAsInt(); txn.TransactionCode = tbTransactionCode.Text; txn.FinancialPaymentDetail.CurrencyTypeValueId = ddlCurrencyType.SelectedValueAsInt(); txn.FinancialPaymentDetail.CreditCardTypeValueId = ddlCreditCardType.SelectedValueAsInt(); txn.Summary = tbSummary.Text; decimal totalAmount = TransactionDetailsState.Select( d => d.Amount ).ToList().Sum(); if ( cbIsRefund.Checked && totalAmount > 0 ) { nbErrorMessage.Title = "Incorrect Refund Amount"; nbErrorMessage.Text = "<p>A refund should have a negative amount. Please unselect the refund option, or change amounts to be negative values.</p>"; nbErrorMessage.Visible = true; return; } if ( cbIsRefund.Checked ) { if ( txn.RefundDetails != null ) { txn.RefundDetails = new FinancialTransactionRefund(); } txn.RefundDetails.RefundReasonValueId = ddlRefundReasonEdit.SelectedValueAsId(); txn.RefundDetails.RefundReasonSummary = tbRefundSummaryEdit.Text; } if ( !Page.IsValid || !txn.IsValid ) { return; } foreach ( var txnDetail in TransactionDetailsState ) { if ( !txnDetail.IsValid ) { return; } } rockContext.WrapTransaction( () => { // Save the transaction rockContext.SaveChanges(); // Delete any transaction details that were removed var txnDetailsInDB = txnDetailService.Queryable().Where( a => a.TransactionId.Equals( txn.Id ) ).ToList(); var deletedDetails = from txnDetail in txnDetailsInDB where !TransactionDetailsState.Select( d => d.Guid ).Contains( txnDetail.Guid ) select txnDetail; deletedDetails.ToList().ForEach( txnDetail => { if ( batchId.HasValue ) { History.EvaluateChange( changes, txnDetail.Account != null ? txnDetail.Account.Name : "Unknown", txnDetail.Amount.FormatAsCurrency(), string.Empty ); } txnDetailService.Delete( txnDetail ); } ); // Save Transaction Details foreach ( var editorTxnDetail in TransactionDetailsState ) { string oldAccountName = string.Empty; string newAccountName = string.Empty; decimal oldAmount = 0.0M; decimal newAmount = 0.0M; // Add or Update the activity type var txnDetail = txn.TransactionDetails.FirstOrDefault( d => d.Guid.Equals( editorTxnDetail.Guid ) ); if ( txnDetail != null ) { oldAccountName = AccountName( txnDetail.AccountId ); oldAmount = txnDetail.Amount; } else { txnDetail = new FinancialTransactionDetail(); txnDetail.Guid = editorTxnDetail.Guid; txn.TransactionDetails.Add( txnDetail ); } newAccountName = AccountName( editorTxnDetail.AccountId ); newAmount = UseSimpleAccountMode ? tbSingleAccountAmount.Text.AsDecimal() : editorTxnDetail.Amount; if ( batchId.HasValue ) { if ( string.IsNullOrWhiteSpace(oldAccountName) ) { History.EvaluateChange( changes, newAccountName, string.Empty, newAmount.FormatAsCurrency() ); } else { if ( oldAccountName == newAccountName ) { if ( oldAmount != newAmount ) { History.EvaluateChange( changes, oldAccountName, oldAmount.FormatAsCurrency(), newAmount.FormatAsCurrency() ); } } else { History.EvaluateChange( changes, oldAccountName, oldAmount.FormatAsCurrency(), string.Empty ); History.EvaluateChange( changes, newAccountName, string.Empty, newAmount.FormatAsCurrency() ); } } } txnDetail.AccountId = editorTxnDetail.AccountId; txnDetail.Amount = newAmount; txnDetail.Summary = editorTxnDetail.Summary; if ( editorTxnDetail.AttributeValues != null ) { txnDetail.LoadAttributes(); txnDetail.AttributeValues = editorTxnDetail.AttributeValues; rockContext.SaveChanges(); txnDetail.SaveAttributeValues( rockContext ); } } // Delete any transaction images that were removed var orphanedBinaryFileIds = new List<int>(); var txnImagesInDB = txnImageService.Queryable().Where( a => a.TransactionId.Equals( txn.Id ) ).ToList(); foreach ( var txnImage in txnImagesInDB.Where( i => !TransactionImagesState.Contains( i.BinaryFileId ) ) ) { changes.Add( "Removed Image" ); orphanedBinaryFileIds.Add( txnImage.BinaryFileId ); txnImageService.Delete( txnImage ); } // Save Transaction Images int imageOrder = 0; foreach ( var binaryFileId in TransactionImagesState ) { // Add or Update the activity type var txnImage = txnImagesInDB.FirstOrDefault( i => i.BinaryFileId == binaryFileId ); if ( txnImage == null ) { changes.Add( "Added Image" ); txnImage = new FinancialTransactionImage(); txnImage.TransactionId = txn.Id; txn.Images.Add( txnImage ); } else { if ( txnImage.BinaryFileId != binaryFileId ) { changes.Add( "Updated Image" ); } } txnImage.BinaryFileId = binaryFileId; txnImage.Order = imageOrder; imageOrder++; } rockContext.SaveChanges(); // Make sure updated binary files are not temporary foreach ( var binaryFile in binaryFileService.Queryable().Where( f => TransactionImagesState.Contains( f.Id ) ) ) { binaryFile.IsTemporary = false; } // Delete any orphaned images foreach ( var binaryFile in binaryFileService.Queryable().Where( f => orphanedBinaryFileIds.Contains( f.Id ) ) ) { binaryFileService.Delete( binaryFile ); } // Update any attributes txn.LoadAttributes(rockContext); txn.FinancialPaymentDetail.LoadAttributes(rockContext); Helper.GetEditValues(phAttributeEdits, txn); Helper.GetEditValues(phPaymentAttributeEdits, txn.FinancialPaymentDetail); // If the transaction is associated with a batch, update that batch's history if ( batchId.HasValue ) { HistoryService.SaveChanges( rockContext, typeof( FinancialBatch ), Rock.SystemGuid.Category.HISTORY_FINANCIAL_TRANSACTION.AsGuid(), batchId.Value, changes, !string.IsNullOrWhiteSpace( newPerson ) ? newPerson : string.Format( "Transaction Id:{0}", txn.Id ), typeof( FinancialTransaction ), txn.Id ); } rockContext.SaveChanges(); txn.SaveAttributeValues(rockContext); txn.FinancialPaymentDetail.SaveAttributeValues(rockContext); } ); // Save selected options to session state in order to prefill values for next added txn Session["NewTxnDefault_BatchId"] = txn.BatchId; Session["NewTxnDefault_TransactionDateTime"] = txn.TransactionDateTime; Session["NewTxnDefault_TransactionType"] = txn.TransactionTypeValueId; Session["NewTxnDefault_SourceType"] = txn.SourceTypeValueId; Session["NewTxnDefault_CurrencyType"] = txn.FinancialPaymentDetail.CurrencyTypeValueId; Session["NewTxnDefault_CreditCardType"] = txn.FinancialPaymentDetail.CreditCardTypeValueId; if ( TransactionDetailsState.Count() == 1 ) { Session["NewTxnDefault_Account"] = TransactionDetailsState.First().AccountId; } else { Session.Remove("NewTxnDefault_Account"); } // Requery the batch to support EF navigation properties var savedTxn = GetTransaction( txn.Id ); if ( savedTxn != null ) { savedTxn.LoadAttributes(); savedTxn.FinancialPaymentDetail.LoadAttributes(); ShowReadOnlyDetails( savedTxn ); } } }
/// <summary> /// Handles the Click event of the btnSaveFinancialTransaction 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 lbSaveTransaction_Click(object sender, EventArgs e) { var rockContext = new RockContext(); var financialTransactionService = new Rock.Model.FinancialTransactionService(rockContext); Rock.Model.FinancialTransaction financialTransaction = null; int financialTransactionId = !string.IsNullOrEmpty(hfIdTransValue.Value) ? int.Parse(hfIdTransValue.Value) : 0; // null if not associated with a batch int?batchId = hfBatchId.Value.AsInteger(); if (financialTransactionId == 0) { financialTransaction = new Rock.Model.FinancialTransaction(); financialTransactionService.Add(financialTransaction); financialTransaction.BatchId = batchId; } else { financialTransaction = financialTransactionService.Get(financialTransactionId); } if (ppAuthorizedPerson.PersonId != null) { financialTransaction.AuthorizedPersonId = ppAuthorizedPerson.PersonId; } else { financialTransaction.AuthorizedPersonId = null; } if (ddlCurrencyType.SelectedItem.ToString() == "Credit Card") { financialTransaction.CreditCardTypeValueId = int.Parse(ddlCreditCardType.SelectedValue); } else { financialTransaction.CreditCardTypeValueId = null; } financialTransaction.CurrencyTypeValueId = int.Parse(ddlCurrencyType.SelectedValue); if (!string.IsNullOrEmpty(ddlPaymentGateway.SelectedValue)) { var gatewayEntity = Rock.Web.Cache.EntityTypeCache.Read(new Guid(ddlPaymentGateway.SelectedValue)); if (gatewayEntity != null) { financialTransaction.GatewayEntityTypeId = gatewayEntity.Id; } } financialTransaction.SourceTypeValueId = int.Parse(ddlSourceType.SelectedValue); financialTransaction.TransactionTypeValueId = int.Parse(ddlTransactionType.SelectedValue); financialTransaction.Summary = tbSummary.Text; financialTransaction.TransactionCode = tbTransactionCode.Text; financialTransaction.TransactionDateTime = dtTransactionDateTime.SelectedDateTime; rockContext.SaveChanges(); if (batchId != null) { Dictionary <string, string> qryString = new Dictionary <string, string>(); qryString["financialBatchid"] = hfBatchId.Value; NavigateToParentPage(qryString); } else { NavigateToParentPage(); } }
/// <summary> /// Handles the Click event of the btnSaveFinancialTransaction 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 lbSaveTransaction_Click( object sender, EventArgs e ) { var rockContext = new RockContext(); var financialTransactionService = new Rock.Model.FinancialTransactionService( rockContext ); Rock.Model.FinancialTransaction financialTransaction = null; int financialTransactionId = !string.IsNullOrEmpty( hfIdTransValue.Value ) ? int.Parse( hfIdTransValue.Value ) : 0; // null if not associated with a batch int? batchId = hfBatchId.Value.AsInteger(); if ( financialTransactionId == 0 ) { financialTransaction = new Rock.Model.FinancialTransaction(); financialTransactionService.Add( financialTransaction ); financialTransaction.BatchId = batchId; } else { financialTransaction = financialTransactionService.Get( financialTransactionId ); } if ( ppAuthorizedPerson.PersonId != null ) { financialTransaction.AuthorizedPersonId = ppAuthorizedPerson.PersonId; } else { financialTransaction.AuthorizedPersonId = null; } if ( ddlCurrencyType.SelectedItem.ToString() == "Credit Card" ) { financialTransaction.CreditCardTypeValueId = int.Parse( ddlCreditCardType.SelectedValue ); } else { financialTransaction.CreditCardTypeValueId = null; } financialTransaction.CurrencyTypeValueId = int.Parse( ddlCurrencyType.SelectedValue ); if ( !string.IsNullOrEmpty( ddlPaymentGateway.SelectedValue ) ) { var gatewayEntity = Rock.Web.Cache.EntityTypeCache.Read( new Guid( ddlPaymentGateway.SelectedValue ) ); if ( gatewayEntity != null ) { financialTransaction.GatewayEntityTypeId = gatewayEntity.Id; } } financialTransaction.SourceTypeValueId = int.Parse( ddlSourceType.SelectedValue ); financialTransaction.TransactionTypeValueId = int.Parse( ddlTransactionType.SelectedValue ); financialTransaction.Summary = tbSummary.Text; financialTransaction.TransactionCode = tbTransactionCode.Text; financialTransaction.TransactionDateTime = dtTransactionDateTime.SelectedDateTime; rockContext.SaveChanges(); if ( batchId != null ) { Dictionary<string, string> qryString = new Dictionary<string, string>(); qryString["financialBatchid"] = hfBatchId.Value; NavigateToParentPage( qryString ); } else { NavigateToParentPage(); } }
/// <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.Read(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.Read(financialPaymentDetail.CurrencyTypeValueId.Value); } if (creditCardTypevalue == null && financialPaymentDetail.CreditCardTypeValueId.HasValue) { creditCardTypevalue = DefinedValueCache.Read(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()); }