// 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); lContent.Text = content; }
/// <summary> /// Migrates the scheduled transaction notes to history. /// </summary> public void MigrateScheduledTransactionNotesToHistory() { var rockContext = new RockContext(); rockContext.Database.CommandTimeout = _commandTimeout; var noteService = new NoteService(rockContext); var historyCategoryId = CategoryCache.Get(Rock.SystemGuid.Category.HISTORY_FINANCIAL_TRANSACTION.AsGuid())?.Id; var entityTypeIdScheduledTransaction = EntityTypeCache.GetId(Rock.SystemGuid.EntityType.FINANCIAL_SCHEDULED_TRANSACTION.AsGuid()); var noteTypeIdScheduledTransaction = NoteTypeCache.GetId(Rock.SystemGuid.NoteType.SCHEDULED_TRANSACTION_NOTE.AsGuid()); if (!historyCategoryId.HasValue || !entityTypeIdScheduledTransaction.HasValue || !noteTypeIdScheduledTransaction.HasValue) { return; } var historyService = new HistoryService(rockContext); var historyQuery = historyService.Queryable().Where(a => a.EntityTypeId == entityTypeIdScheduledTransaction.Value); var captionsToConvert = new string[] { "Created Transaction" , "Updated Transaction" , "Cancelled Transaction" , "Reactivated Transaction" }; var notesToConvertToHistory = noteService.Queryable() .Where(a => a.NoteTypeId == noteTypeIdScheduledTransaction.Value && captionsToConvert.Contains(a.Caption) && a.EntityId.HasValue) .Where(a => !historyQuery.Any(h => h.EntityId == a.EntityId)); var notesToConvertToSummaryList = noteService.Queryable() .Where(a => a.NoteTypeId == noteTypeIdScheduledTransaction.Value && a.Caption == "Created Transaction" && !string.IsNullOrEmpty(a.Text) && a.EntityId.HasValue) .AsNoTracking().ToList(); List <History> historyRecordsToInsert = notesToConvertToHistory.AsNoTracking() .ToList() .Select(n => { var historyRecord = new History { CategoryId = historyCategoryId.Value, EntityTypeId = entityTypeIdScheduledTransaction.Value, EntityId = n.EntityId.Value, Guid = Guid.NewGuid(), CreatedByPersonAliasId = n.CreatedByPersonAliasId, ModifiedByPersonAliasId = n.ModifiedByPersonAliasId, CreatedDateTime = n.CreatedDateTime, ModifiedDateTime = n.ModifiedDateTime }; if (n.Caption == "Cancelled Transaction") { historyRecord.Verb = "MODIFY"; historyRecord.ChangeType = "Property"; historyRecord.ValueName = "Is Active"; historyRecord.NewValue = "False"; } else if (n.Caption == "Reactivated Transaction") { historyRecord.Verb = "MODIFY"; historyRecord.ChangeType = "Property"; historyRecord.ValueName = "Is Active"; historyRecord.NewValue = "True"; } else if (n.Caption == "Updated Transaction") { historyRecord.Verb = "MODIFY"; historyRecord.ValueName = "Transaction"; } else { historyRecord.Verb = "ADD"; historyRecord.ChangeType = "Record"; historyRecord.ValueName = "Transaction"; } return(historyRecord); }).ToList(); rockContext.BulkInsert(historyRecordsToInsert); var qryNotesToDelete = noteService.Queryable().Where(a => a.NoteTypeId == noteTypeIdScheduledTransaction && captionsToConvert.Contains(a.Caption)); rockContext.BulkDelete(qryNotesToDelete); foreach (var noteToConvertToSummary in notesToConvertToSummaryList) { using (var updatedSummaryContext = new RockContext()) { var scheduledTransactionService = new FinancialScheduledTransactionService(updatedSummaryContext); var scheduledTransaction = scheduledTransactionService.Get(noteToConvertToSummary.EntityId.Value); if (scheduledTransaction != null && scheduledTransaction.Summary.IsNullOrWhiteSpace()) { scheduledTransaction.Summary = noteToConvertToSummary.Text; updatedSummaryContext.SaveChanges(disablePrePostProcessing: true); } } } }
/// <summary> /// Binds the grid. /// </summary> private void BindGrid(bool isExporting = false) { 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(); // Valid Accounts var accountGuids = GetAttributeValue("Accounts").SplitDelimitedValues().AsGuidList(); if (accountGuids.Any()) { qry = qry.Where(t => t.ScheduledTransactionDetails.Any(d => accountGuids.Contains(d.Account.Guid))); } // 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) && ddlAccount.Visible) { 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); } _isExporting = isExporting; gList.SetLinqDataSource <FinancialScheduledTransaction>(qry); gList.DataBind(); _isExporting = false; } }
/// <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); } if (systemEmail == null) { throw new Exception("Expiring credit card email is missing."); } // Fetch the configured Workflow once if one was set, we'll use it later. Guid?workflowGuid = dataMap.GetString("Workflow").AsGuidOrNull(); WorkflowTypeCache workflowType = null; var workflowService = new WorkflowService(rockContext); if (workflowGuid != null) { workflowType = WorkflowTypeCache.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(); // Get the current month and year DateTime now = DateTime.Now; int month = now.Month; int year = now.Year; int counter = 0; var errors = new List <string>(); foreach (var transaction in qry) { int?expirationMonthDecrypted = Encryption.DecryptString(transaction.FinancialPaymentDetail.ExpirationMonthEncrypted).AsIntegerOrNull(); int?expirationYearDecrypted = Encryption.DecryptString(transaction.FinancialPaymentDetail.ExpirationYearEncrypted).AsIntegerOrNull(); if (expirationMonthDecrypted.HasValue && expirationMonthDecrypted.HasValue) { string acctNum = string.Empty; if (!string.IsNullOrEmpty(transaction.FinancialPaymentDetail.AccountNumberMasked) && transaction.FinancialPaymentDetail.AccountNumberMasked.Length >= 4) { acctNum = transaction.FinancialPaymentDetail.AccountNumberMasked.Substring(transaction.FinancialPaymentDetail.AccountNumberMasked.Length - 4); } int warningYear = expirationYearDecrypted.Value; int warningMonth = expirationMonthDecrypted.Value - 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 <RockEmailMessageRecipient>(); var mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields(null); var person = transaction.AuthorizedPersonAlias.Person; if (!person.IsEmailActive || person.Email.IsNullOrWhiteSpace() || person.EmailPreference == EmailPreference.DoNotEmail) { continue; } mergeFields.Add("Person", person); mergeFields.Add("Card", acctNum); mergeFields.Add("Expiring", expirationDate); recipients.Add(new RockEmailMessageRecipient(person, mergeFields)); var emailMessage = new RockEmailMessage(systemEmail.Guid); emailMessage.SetRecipients(recipients); var emailErrors = new List <string>(); emailMessage.Send(out emailErrors); errors.AddRange(emailErrors); // 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); if (errors.Any()) { StringBuilder sb = new StringBuilder(); sb.AppendLine(); sb.Append(string.Format("{0} Errors: ", errors.Count())); errors.ForEach(e => { sb.AppendLine(); sb.Append(e); }); string errorMessage = sb.ToString(); context.Result += errorMessage; var exception = new Exception(errorMessage); HttpContext context2 = HttpContext.Current; ExceptionLogService.LogException(exception, context2); throw exception; } }
protected void btnDownload_Click(object sender, EventArgs e) { string batchNamePrefix = GetAttributeValue("BatchNamePrefix"); Guid? receiptEmail = GetAttributeValue("ReceiptEmail").AsGuidOrNull(); Guid? failedPaymentEmail = GetAttributeValue("FailedPaymentEmail").AsGuidOrNull(); Guid? failedPaymentWorkflowType = GetAttributeValue("FailedPaymentWorkflow").AsGuidOrNull(); DateTime?startDateTime = drpDates.LowerValue; DateTime?endDateTime = drpDates.UpperValue; if (startDateTime.HasValue && endDateTime.HasValue && endDateTime.Value.CompareTo(startDateTime.Value) >= 0) { var financialGateway = GetSelectedGateway(); if (financialGateway != null) { var gateway = financialGateway.GetGatewayComponent(); if (gateway != null) { DateTime start = startDateTime.Value; DateTime end = endDateTime.Value.AddDays(1); string errorMessage = string.Empty; var payments = gateway.GetPayments(financialGateway, start, end, out errorMessage); if (string.IsNullOrWhiteSpace(errorMessage)) { var qryParam = new Dictionary <string, string>(); qryParam.Add("batchId", "9999"); string batchUrlFormat = LinkedPageUrl("BatchDetailPage", qryParam).Replace("9999", "{0}"); string resultSummary = FinancialScheduledTransactionService.ProcessPayments(financialGateway, batchNamePrefix, payments, batchUrlFormat, receiptEmail, failedPaymentEmail, failedPaymentWorkflowType); if (!string.IsNullOrWhiteSpace(resultSummary)) { nbSuccess.Text = string.Format("<ul>{0}</ul>", resultSummary); } else { nbSuccess.Text = string.Format("There were not any transactions downloaded.", resultSummary); } nbSuccess.Visible = true; } else { ShowError(errorMessage); } } else { ShowError("Selected Payment Gateway does not have a valid payment processor!"); } } else { ShowError("Please select a valid Payment Gateway!"); } } else { ShowError("Please select a valid Date Range!"); } }
/// <summary> /// Updates the scheduled payment. /// </summary> /// <param name="usePaymentToken">if set to <c>true</c> [use payment token].</param> /// <param name="paymentToken">The payment token.</param> protected void UpdateScheduledPayment(bool usePaymentToken, string paymentToken = null) { var giftTerm = this.GetAttributeValue(AttributeKey.GiftTerm); if (dtpStartDate.SelectedDate <= RockDateTime.Today) { nbUpdateScheduledPaymentWarning.Visible = true; nbUpdateScheduledPaymentWarning.Text = string.Format("When scheduling a {0}, make sure the starting date is in the future (after today)", giftTerm.ToLower()); return; } var rockContext = new RockContext(); var financialScheduledTransactionService = new FinancialScheduledTransactionService(rockContext); var financialScheduledTransactionDetailService = new FinancialScheduledTransactionDetailService(rockContext); Guid scheduledTransactionGuid = hfScheduledTransactionGuid.Value.AsGuid(); var financialScheduledTransaction = financialScheduledTransactionService.Get(scheduledTransactionGuid); financialScheduledTransaction.StartDate = dtpStartDate.SelectedDate.Value; financialScheduledTransaction.TransactionFrequencyValueId = ddlFrequency.SelectedValue.AsInteger(); ReferencePaymentInfo referencePaymentInfo; var person = financialScheduledTransaction.AuthorizedPersonAlias.Person; string errorMessage; var financialGateway = this.FinancialGateway; var financialGatewayComponent = this.FinancialGatewayComponent; var existingPaymentOrPersonSavedAccountId = rblExistingPaymentOrPersonSavedAccount.SelectedValue.AsInteger(); bool useExistingPaymentMethod = pnlUseExistingPaymentNoSavedAccounts.Visible || existingPaymentOrPersonSavedAccountId == 0; bool useSavedAccount = pnlUseExistingPaymentWithSavedAccounts.Visible && existingPaymentOrPersonSavedAccountId > 0; if (usePaymentToken) { referencePaymentInfo = new ReferencePaymentInfo(); referencePaymentInfo.FirstName = person.FirstName; referencePaymentInfo.LastName = person.LastName; referencePaymentInfo.UpdateAddressFieldsFromAddressControl(acBillingAddress); referencePaymentInfo.ReferenceNumber = paymentToken; var customerToken = financialGatewayComponent.CreateCustomerAccount(this.FinancialGateway, referencePaymentInfo, out errorMessage); if (errorMessage.IsNotNullOrWhiteSpace() || customerToken.IsNullOrWhiteSpace()) { nbMessage.NotificationBoxType = NotificationBoxType.Danger; nbMessage.Text = errorMessage ?? "Unknown Error"; nbMessage.Visible = true; return; } referencePaymentInfo.GatewayPersonIdentifier = customerToken; } else if (useExistingPaymentMethod) { // use save payment method as original transaction referencePaymentInfo = new ReferencePaymentInfo(); referencePaymentInfo.GatewayPersonIdentifier = financialScheduledTransaction.FinancialPaymentDetail.GatewayPersonIdentifier; referencePaymentInfo.FinancialPersonSavedAccountId = financialScheduledTransaction.FinancialPaymentDetail.FinancialPersonSavedAccountId; } else if (useSavedAccount) { var savedAccount = new FinancialPersonSavedAccountService(rockContext).Get(existingPaymentOrPersonSavedAccountId); if (savedAccount != null) { referencePaymentInfo = savedAccount.GetReferencePayment(); } else { // shouldn't happen throw new Exception("Unable to determine Saved Account"); } } else { // shouldn't happen throw new Exception("Unable to determine payment method"); } var selectedAccountAmounts = caapPromptForAccountAmounts.AccountAmounts.Where(a => a.Amount.HasValue && a.Amount.Value != 0).Select(a => new { a.AccountId, Amount = a.Amount.Value }).ToArray(); referencePaymentInfo.Amount = selectedAccountAmounts.Sum(a => a.Amount); var originalGatewayScheduleId = financialScheduledTransaction.GatewayScheduleId; try { financialScheduledTransaction.FinancialPaymentDetail.ClearPaymentInfo(); var successfullyUpdated = financialGatewayComponent.UpdateScheduledPayment(financialScheduledTransaction, referencePaymentInfo, out errorMessage); if (!successfullyUpdated) { nbMessage.NotificationBoxType = NotificationBoxType.Danger; nbMessage.Text = errorMessage ?? "Unknown Error"; nbMessage.Visible = true; return; } financialScheduledTransaction.FinancialPaymentDetail.SetFromPaymentInfo(referencePaymentInfo, financialGatewayComponent as GatewayComponent, rockContext); var selectedAccountIds = selectedAccountAmounts.Select(a => a.AccountId).ToArray(); var deletedTransactionDetails = financialScheduledTransaction.ScheduledTransactionDetails.ToList().Where(a => !selectedAccountIds.Contains(a.AccountId)).ToList(); foreach (var deletedTransactionDetail in deletedTransactionDetails) { financialScheduledTransaction.ScheduledTransactionDetails.Remove(deletedTransactionDetail); financialScheduledTransactionDetailService.Delete(deletedTransactionDetail); } foreach (var selectedAccountAmount in selectedAccountAmounts) { var scheduledTransactionDetail = financialScheduledTransaction.ScheduledTransactionDetails.FirstOrDefault(a => a.AccountId == selectedAccountAmount.AccountId); if (scheduledTransactionDetail == null) { scheduledTransactionDetail = new FinancialScheduledTransactionDetail(); scheduledTransactionDetail.AccountId = selectedAccountAmount.AccountId; financialScheduledTransaction.ScheduledTransactionDetails.Add(scheduledTransactionDetail); } scheduledTransactionDetail.Amount = selectedAccountAmount.Amount; } rockContext.SaveChanges(); Task.Run(() => ScheduledGiftWasModifiedMessage.PublishScheduledTransactionEvent(financialScheduledTransaction.Id, ScheduledGiftEventTypes.ScheduledGiftUpdated)); } catch (Exception) { // if the GatewayScheduleId was updated, but there was an exception, // make sure we save the financialScheduledTransaction record with the updated GatewayScheduleId so we don't orphan it if (financialScheduledTransaction.GatewayScheduleId.IsNotNullOrWhiteSpace() && (originalGatewayScheduleId != financialScheduledTransaction.GatewayScheduleId)) { rockContext.SaveChanges(); } throw; } var mergeFields = LavaHelper.GetCommonMergeFields(this.RockPage, this.CurrentPerson, new CommonMergeFieldsOptions { GetLegacyGlobalMergeFields = false }); var finishLavaTemplate = this.GetAttributeValue(AttributeKey.FinishLavaTemplate); // re-fetch financialScheduledTransaction with a new RockContext from database to ensure that lazy loaded fields will be populated using (var rockContextForSummary = new RockContext()) { if (pnlHostedPaymentControl.Visible && hfSaveNewAccount.Value.AsInteger() == 1 && tbSaveAccount.Text.IsNotNullOrWhiteSpace()) { SaveNewFinancialPersonSavedAccount(financialScheduledTransaction); } financialScheduledTransaction = new FinancialScheduledTransactionService(rockContextForSummary).Get(scheduledTransactionGuid); mergeFields.Add("Transaction", financialScheduledTransaction); mergeFields.Add("Person", financialScheduledTransaction.AuthorizedPersonAlias.Person); mergeFields.Add("PaymentDetail", financialScheduledTransaction.FinancialPaymentDetail); mergeFields.Add("BillingLocation", financialScheduledTransaction.FinancialPaymentDetail.BillingLocation); pnlPromptForChanges.Visible = false; pnlTransactionSummary.Visible = true; lTransactionSummaryHTML.Text = finishLavaTemplate.ResolveMergeFields(mergeFields); } }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> /// <exception cref="Exception"> /// One or more exceptions occurred while downloading transactions..." + Environment.NewLine + exceptionMsgs.AsDelimited( Environment.NewLine ) /// </exception> public virtual void Execute(IJobExecutionContext context) { var exceptionMsgs = new List <string>(); // get the job map var dataMap = context.JobDetail.JobDataMap; var scheduledPaymentsProcessed = 0; Guid?receiptEmail = dataMap.GetString(AttributeKey.ReceiptEmail).AsGuidOrNull(); Guid?failedPaymentEmail = dataMap.GetString(AttributeKey.FailedPaymentEmail).AsGuidOrNull(); Guid?failedPaymentWorkflowType = dataMap.GetString(AttributeKey.FailedPaymentWorkflow).AsGuidOrNull(); int daysBack = dataMap.GetString(AttributeKey.DaysBack).AsIntegerOrNull() ?? 1; DateTime today = RockDateTime.Today; TimeSpan daysBackTimeSpan = new TimeSpan(daysBack, 0, 0, 0); string batchNamePrefix = dataMap.GetString(AttributeKey.BatchNamePrefix); using (var rockContext = new RockContext()) { var targetGatewayQuery = new FinancialGatewayService(rockContext).Queryable().Where(g => g.IsActive).AsNoTracking(); var targetGatewayGuid = dataMap.GetString(AttributeKey.TargetGateway).AsGuidOrNull(); if (targetGatewayGuid.HasValue) { targetGatewayQuery = targetGatewayQuery.Where(g => g.Guid == targetGatewayGuid.Value); } foreach (var financialGateway in targetGatewayQuery.ToList()) { try { financialGateway.LoadAttributes(rockContext); var gateway = financialGateway.GetGatewayComponent(); if (gateway == null) { continue; } DateTime endDateTime = today.Add(financialGateway.GetBatchTimeOffset()); // If the calculated end time has not yet occurred, use the previous day. endDateTime = RockDateTime.Now.CompareTo(endDateTime) >= 0 ? endDateTime : endDateTime.AddDays(-1); DateTime startDateTime = endDateTime.Subtract(daysBackTimeSpan); string errorMessage = string.Empty; var payments = gateway.GetPayments(financialGateway, startDateTime, endDateTime, out errorMessage); if (string.IsNullOrWhiteSpace(errorMessage)) { FinancialScheduledTransactionService.ProcessPayments(financialGateway, batchNamePrefix, payments, string.Empty, receiptEmail, failedPaymentEmail, failedPaymentWorkflowType); scheduledPaymentsProcessed += payments.Count(); } else { throw new Exception(errorMessage); } } catch (Exception ex) { ExceptionLogService.LogException(ex, null); exceptionMsgs.Add(ex.Message); } } } if (exceptionMsgs.Any()) { throw new Exception("One or more exceptions occurred while downloading transactions..." + Environment.NewLine + exceptionMsgs.AsDelimited(Environment.NewLine)); } context.Result = string.Format("{0} payments processed", scheduledPaymentsProcessed); }
/// <summary> /// Updates the scheduled payment. /// </summary> /// <param name="usePaymentToken">if set to <c>true</c> [use payment token].</param> /// <param name="paymentToken">The payment token.</param> protected void UpdateScheduledPayment(bool usePaymentToken, string paymentToken = null) { var giftTerm = this.GetAttributeValue(AttributeKey.GiftTerm); if (dtpStartDate.SelectedDate <= RockDateTime.Today) { nbUpdateScheduledPaymentWarning.Visible = true; nbUpdateScheduledPaymentWarning.Text = string.Format("When scheduling a {0}, make sure the starting date is in the future (after today)", giftTerm.ToLower()); return; } var rockContext = new RockContext(); var financialScheduledTransactionService = new FinancialScheduledTransactionService(rockContext); var financialScheduledTransactionDetailService = new FinancialScheduledTransactionDetailService(rockContext); int scheduledTransactionId = hfScheduledTransactionId.Value.AsInteger(); var financialScheduledTransaction = financialScheduledTransactionService.Get(scheduledTransactionId); financialScheduledTransaction.StartDate = dtpStartDate.SelectedDate.Value; financialScheduledTransaction.TransactionFrequencyValueId = ddlFrequency.SelectedValue.AsInteger(); ReferencePaymentInfo referencePaymentInfo; var person = financialScheduledTransaction.AuthorizedPersonAlias.Person; string errorMessage; var financialGateway = this.FinancialGateway; var financialGatewayComponent = this.FinancialGatewayComponent; if (usePaymentToken) { referencePaymentInfo = new ReferencePaymentInfo(); referencePaymentInfo.FirstName = person.FirstName; referencePaymentInfo.LastName = person.LastName; referencePaymentInfo.UpdateAddressFieldsFromAddressControl(acBillingAddress); referencePaymentInfo.ReferenceNumber = paymentToken; var customerToken = financialGatewayComponent.CreateCustomerAccount(this.FinancialGateway, referencePaymentInfo, out errorMessage); if (errorMessage.IsNotNullOrWhiteSpace() || customerToken.IsNullOrWhiteSpace()) { nbMessage.NotificationBoxType = NotificationBoxType.Danger; nbMessage.Text = errorMessage ?? "Unknown Error"; nbMessage.Visible = true; return; } referencePaymentInfo.GatewayPersonIdentifier = customerToken; } else { var savedAccountId = ddlPersonSavedAccount.SelectedValue.AsInteger(); var savedAccount = new FinancialPersonSavedAccountService(rockContext).Get(savedAccountId); if (savedAccount != null) { referencePaymentInfo = savedAccount.GetReferencePayment(); } else { throw new Exception("Unable to determine Saved Account"); } } var selectedAccountAmounts = caapPromptForAccountAmounts.AccountAmounts.Where(a => a.Amount.HasValue && a.Amount.Value != 0).Select(a => new { a.AccountId, Amount = a.Amount.Value }).ToArray(); referencePaymentInfo.Amount = selectedAccountAmounts.Sum(a => a.Amount); var successfullyUpdated = financialGatewayComponent.UpdateScheduledPayment(financialScheduledTransaction, referencePaymentInfo, out errorMessage); if (!successfullyUpdated) { nbMessage.NotificationBoxType = NotificationBoxType.Danger; nbMessage.Text = errorMessage ?? "Unknown Error"; nbMessage.Visible = true; return; } financialScheduledTransaction.FinancialPaymentDetail.SetFromPaymentInfo(referencePaymentInfo, financialGatewayComponent as GatewayComponent, rockContext); var selectedAccountIds = selectedAccountAmounts.Select(a => a.AccountId).ToArray(); var deletedTransactionDetails = financialScheduledTransaction.ScheduledTransactionDetails.ToList().Where(a => !selectedAccountIds.Contains(a.AccountId)).ToList(); foreach (var deletedTransactionDetail in deletedTransactionDetails) { financialScheduledTransaction.ScheduledTransactionDetails.Remove(deletedTransactionDetail); financialScheduledTransactionDetailService.Delete(deletedTransactionDetail); } foreach (var selectedAccountAmount in selectedAccountAmounts) { var scheduledTransactionDetail = financialScheduledTransaction.ScheduledTransactionDetails.FirstOrDefault(a => a.AccountId == selectedAccountAmount.AccountId); if (scheduledTransactionDetail == null) { scheduledTransactionDetail = new FinancialScheduledTransactionDetail(); scheduledTransactionDetail.AccountId = selectedAccountAmount.AccountId; financialScheduledTransaction.ScheduledTransactionDetails.Add(scheduledTransactionDetail); } scheduledTransactionDetail.Amount = selectedAccountAmount.Amount; } rockContext.SaveChanges(); var mergeFields = LavaHelper.GetCommonMergeFields(this.RockPage, this.CurrentPerson, new CommonMergeFieldsOptions { GetLegacyGlobalMergeFields = false }); var finishLavaTemplate = this.GetAttributeValue(AttributeKey.FinishLavaTemplate); mergeFields.Add("Transaction", financialScheduledTransaction); mergeFields.Add("Person", financialScheduledTransaction.AuthorizedPersonAlias.Person); mergeFields.Add("PaymentDetail", financialScheduledTransaction.FinancialPaymentDetail); mergeFields.Add("BillingLocation", financialScheduledTransaction.FinancialPaymentDetail.BillingLocation); pnlPromptForChanges.Visible = false; pnlTransactionSummary.Visible = true; lTransactionSummaryHTML.Text = finishLavaTemplate.ResolveMergeFields(mergeFields); }
/// <summary> /// Shows the details. /// </summary> private void ShowDetails() { var rockContext = new RockContext(); var scheduledTransaction = this.GetFinancialScheduledTransaction(rockContext); if (scheduledTransaction == null) { // Note: Also verified in OnInit ShowConfigurationMessage(NotificationBoxType.Warning, "Warning", "Scheduled Transaction not found."); return; } var financialScheduledTransactionService = new FinancialScheduledTransactionService(rockContext); string errorMessages; if (!financialScheduledTransactionService.GetStatus(scheduledTransaction, out errorMessages)) { ShowConfigurationMessage(NotificationBoxType.Danger, "Error", errorMessages); return; } hfScheduledTransactionGuid.Value = scheduledTransaction.Guid.ToString(); List <int> selectableAccountIds = new FinancialAccountService(rockContext).GetByGuids(this.GetAttributeValues(AttributeKey.AccountsToDisplay).AsGuidList()).Select(a => a.Id).ToList(); CampusAccountAmountPicker.AccountIdAmount[] accountAmounts = scheduledTransaction.ScheduledTransactionDetails.Select(a => new CampusAccountAmountPicker.AccountIdAmount(a.AccountId, a.Amount)).ToArray(); // if the scheduledTransaction already has Multiple Account, enabled multi account mode. Otherwise, only enabled multi account based on the block setting. var hasMultipleAccounts = accountAmounts.Length > 1; bool enableMultiAccount = hasMultipleAccounts || this.GetAttributeValue(AttributeKey.EnableMultiAccount).AsBoolean(); if (enableMultiAccount) { caapPromptForAccountAmounts.AmountEntryMode = CampusAccountAmountPicker.AccountAmountEntryMode.MultipleAccounts; } else { caapPromptForAccountAmounts.AmountEntryMode = CampusAccountAmountPicker.AccountAmountEntryMode.SingleAccount; } caapPromptForAccountAmounts.AskForCampusIfKnown = this.GetAttributeValue(AttributeKey.AskForCampusIfKnown).AsBoolean(); caapPromptForAccountAmounts.SelectableAccountIds = selectableAccountIds.ToArray(); if (!caapPromptForAccountAmounts.SelectableAccountIds.Any()) { ShowConfigurationMessage(NotificationBoxType.Warning, "Configuration", "At least one Financial Account must be selected in the configuration for this block."); pnlPromptForChanges.Visible = false; return; } if (this.FinancialGateway == null) { ShowConfigurationMessage(NotificationBoxType.Warning, "Configuration", "Unable to determine the financial gateway for this scheduled transaction."); pnlPromptForChanges.Visible = false; return; } if (this.FinancialGatewayComponent == null || !(this.FinancialGatewayComponent is IHostedGatewayComponent)) { ShowConfigurationMessage(NotificationBoxType.Danger, "Configuration", "This page is not configured to allow edits for the payment gateway associated with the selected transaction."); pnlPromptForChanges.Visible = false; return; } caapPromptForAccountAmounts.AccountAmounts = accountAmounts; var targetPerson = scheduledTransaction.AuthorizedPersonAlias.Person; SetAccountPickerCampus(targetPerson); int oneTimeFrequencyId = DefinedValueCache.GetId(Rock.SystemGuid.DefinedValue.TRANSACTION_FREQUENCY_ONE_TIME.AsGuid()) ?? 0; ddlFrequency.Items.Clear(); var supportedFrequencies = this.FinancialGatewayComponent.SupportedPaymentSchedules; foreach (var supportedFrequency in supportedFrequencies) { // If this isn't a one-time scheduled transaction, don't allow changing scheduled transaction to a one-time, if (scheduledTransaction.TransactionFrequencyValueId == oneTimeFrequencyId || supportedFrequency.Id != oneTimeFrequencyId) { ddlFrequency.Items.Add(new ListItem(supportedFrequency.Value, supportedFrequency.Id.ToString())); } } ddlFrequency.SetValue(scheduledTransaction.TransactionFrequencyValueId); /* 2020-02-26 MDP: Payment prompt behavior.. * - No Saved Accounts * - Show text with existing payment method with a 'Change' link. * - If 'Change' is clicked, existing payment info prompt will disappear and hosted payment will be displayed * - Has Saved Accounts * - Show RadioButtons with first item with the existing payment as the option, followed by saved accounts * - Then under the RadioButtons show a 'Add Method'. * - If 'Add Method' is clicked, RadioButtons will disappear and hosted payment will be displayed */ string paymentName; if (scheduledTransaction.FinancialPaymentDetail.FinancialPersonSavedAccountId.HasValue) { paymentName = scheduledTransaction.FinancialPaymentDetail.FinancialPersonSavedAccount.Name; } else { paymentName = scheduledTransaction.FinancialPaymentDetail.CurrencyTypeValue?.Value; } string existingPaymentInfoDisplayText; if (scheduledTransaction.FinancialPaymentDetail.ExpirationDate.IsNotNullOrWhiteSpace()) { existingPaymentInfoDisplayText = $"Existing Payment Method - {paymentName} ({scheduledTransaction.FinancialPaymentDetail.AccountNumberMasked} Expires: {scheduledTransaction.FinancialPaymentDetail.ExpirationDate})"; } else { existingPaymentInfoDisplayText = $"Existing Payment Method - {paymentName} ({scheduledTransaction.FinancialPaymentDetail.AccountNumberMasked})"; } lUseExistingPaymentMethodNoSavedAccounts.Text = existingPaymentInfoDisplayText; var personSavedAccountList = GetSavedAccounts(); pnlHostedPaymentControl.Visible = false; if (personSavedAccountList.Any()) { pnlUseExistingPaymentWithSavedAccounts.Visible = true; pnlUseExistingPaymentNoSavedAccounts.Visible = false; BindPersonSavedAccounts(personSavedAccountList); rblExistingPaymentOrPersonSavedAccount.Items.Insert(0, new ListItem(existingPaymentInfoDisplayText, "0")); // default to using existing payment method rblExistingPaymentOrPersonSavedAccount.SetValue(0); } else { // no saved account, so just prompt for payment info (or using existing payment info) pnlUseExistingPaymentNoSavedAccounts.Visible = true; pnlUseExistingPaymentWithSavedAccounts.Visible = false; } dtpStartDate.SelectedDate = scheduledTransaction.NextPaymentDate; // NOTE: Depending on the Gateway, the earliest date could be more than 1-2+ days in the future var earliestScheduledStartDate = FinancialGatewayComponent.GetEarliestScheduledStartDate(FinancialGateway); if (dtpStartDate.SelectedDate.HasValue && dtpStartDate.SelectedDate.Value < earliestScheduledStartDate) { dtpStartDate.SelectedDate = earliestScheduledStartDate; } var person = scheduledTransaction.AuthorizedPersonAlias.Person; Location billingLocation = null; // default to the billing location of the scheduled transaction, or the mailing location if unable to get a billing location from the scheduled transaction. if (scheduledTransaction.FinancialPaymentDetail != null) { billingLocation = scheduledTransaction.FinancialPaymentDetail.BillingLocation; } if (billingLocation == null) { billingLocation = person.GetMailingLocation(); } acBillingAddress.SetValues(billingLocation); }
/// <summary> /// Gets the message data for a <see cref="ScheduledGiftWasModifiedMessage"/>. /// </summary> /// <param name="rockContext">The rock context.</param> /// <param name="scheduledTransactionId">The scheduled transaction identifier.</param> /// <param name="personSearchKeyTypeGuid">The person search key type unique identifier.</param> /// <param name="gatewaySupportedCardTypesDefinedValueGuid">[Optional] The <see cref="Guid"/> of the <see cref="DefinedValue"/> that indicates the credit card types supported by the <see cref="FinancialGateway"/> for a specified currency.</param> /// <param name="gatewayCurrencyUnitMultiple">[Optional] The <see cref="Guid"/> of the <see cref="DefinedValue"/> that indicates the "unit multiple" (e.g., 100 for dollars) of the currency specified by the gatway.</param> /// <returns></returns> private static ScheduledGiftWasModifiedMessageData GetScheduledGiftWasModifiedMessageData(RockContext rockContext, int scheduledTransactionId, Guid?personSearchKeyTypeGuid, Guid?gatewaySupportedCardTypesDefinedValueGuid, Guid?gatewayCurrencyUnitMultiple) { var status = "success"; var statusMessage = string.Empty; var scheduledTransactionService = new FinancialScheduledTransactionService(rockContext); var scheduledTransaction = scheduledTransactionService.Queryable() .AsNoTracking() .Where(s => s.Id == scheduledTransactionId) .FirstOrDefault(); var data = new ScheduledGiftWasModifiedMessageData { Person = new TransactionPersonView { PrimaryAliasId = scheduledTransaction.AuthorizedPersonAliasId, Id = scheduledTransaction.AuthorizedPersonAlias.Person.Id, Guid = scheduledTransaction.Guid, FirstName = scheduledTransaction.AuthorizedPersonAlias.Person.FirstName, NickName = scheduledTransaction.AuthorizedPersonAlias.Person.NickName, LastName = scheduledTransaction.AuthorizedPersonAlias.Person.LastName, Email = scheduledTransaction.AuthorizedPersonAlias.Person.Email, ForeignId = scheduledTransaction.AuthorizedPersonAlias.Person.ForeignId, }, FinancialScheduledTransaction = new ScheduledTransactionView { Id = scheduledTransaction.Id, Guid = scheduledTransaction.Guid, CreditCardTypeValueId = scheduledTransaction.FinancialPaymentDetail.CreditCardTypeValueId, CurrencyTypeValueId = scheduledTransaction.FinancialPaymentDetail.CurrencyTypeValueId, ForeignCurrencyCode = new TransactionCurrencyCodeView { ValueId = scheduledTransaction.ForeignCurrencyCodeValueId, }, ScheduledTransactionId = scheduledTransaction.Id, TransactionFrequencyValue = new TransactionFrequencyView { ValueId = scheduledTransaction.TransactionFrequencyValueId, }, SourceTypeValueId = scheduledTransaction.SourceTypeValueId, Status = status, StatusMessage = statusMessage, TransactionCode = scheduledTransaction.TransactionCode, TransactionTypeValueId = scheduledTransaction.TransactionTypeValueId, ForeignKey = scheduledTransaction.ForeignKey, Details = scheduledTransaction.ScheduledTransactionDetails.Select(d => new ScheduledTransactionDetailView { Id = d.Id, Guid = d.Guid, AccountId = d.AccountId, AccountName = d.Account.Name, PublicAccountName = d.Account.PublicName, /* Shaun Cummings - September 10, 2021 * * For scheduled transactions, if the transaction has a ForeignCurrencyCode, the Amount property * reflects the amount in that foreign currency. * */ Amount = d.Amount }).ToList() } }; if (data != null) { data.Person.HydratePersonData(rockContext, data.Address, personSearchKeyTypeGuid); data.FinancialScheduledTransaction.HydrateDefinedValues(gatewaySupportedCardTypesDefinedValueGuid, gatewayCurrencyUnitMultiple); } return(data); }
/// <summary> /// Handles the Click event of the btnMigrateScheduledTransactions 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 btnMigrateScheduledTransactions_Click(object sender, EventArgs e) { var rockContext = new RockContext(); var binaryFileService = new BinaryFileService(rockContext); var binaryFileId = fuScheduleImportFile.BinaryFileId; BinaryFile binaryFile = null; if (binaryFileId.HasValue) { binaryFile = binaryFileService.Get(binaryFileId.Value); } Dictionary <string, string> subscriptionImportRecordLookup = null; var importData = binaryFile.ContentsToString(); StringReader stringReader = new StringReader(importData); CsvReader csvReader = new CsvReader(stringReader); csvReader.Configuration.HasHeaderRecord = false; subscriptionImportRecordLookup = csvReader.GetRecords <SubscriptionCustomerImportRecord>().ToDictionary(k => k.NMISubscriptionId, v => v.PiCustomerId); var financialGatewayService = new FinancialGatewayService(rockContext); var nmiFinancialGatewayId = ddlNMIGateway.SelectedValue.AsInteger(); var nmiFinancialGateway = financialGatewayService.Get(nmiFinancialGatewayId); var nmiGatewayComponent = nmiFinancialGateway.GetGatewayComponent(); var piFinancialGatewayId = ddlPiGateway.SelectedValue.AsInteger(); var piFinancialGateway = financialGatewayService.Get(piFinancialGatewayId); var piGatewayComponent = piFinancialGateway.GetGatewayComponent() as IHostedGatewayComponent; var financialScheduledTransactionService = new FinancialScheduledTransactionService(rockContext); // Get the ScheduledTransaction with NoTracking. If we need to update it, we'll track it with a different rockContext then save it. // Limit to active subscriptions that have a NextPaymentDate (onetime or canceled schedules might not have a NextPaymentDate) var scheduledTransactions = financialScheduledTransactionService.Queryable().Where(a => a.FinancialGatewayId == nmiFinancialGatewayId & a.IsActive && a.NextPaymentDate.HasValue).AsNoTracking().ToList(); var earliestPiStartDate = piGatewayComponent.GetEarliestScheduledStartDate(piFinancialGateway); var oneTimeFrequencyId = DefinedValueCache.GetId(Rock.SystemGuid.DefinedValue.TRANSACTION_FREQUENCY_ONE_TIME.AsGuid()); string errorMessage; var scheduledTransactionResultsBuilder = new StringBuilder(); var scheduledTransactionCount = scheduledTransactions.Count(); var scheduledTransactionProgress = 0; // Migrating Scheduled Transactions might take a while. Each migrated Scheduled Payment may take a half second or so to create on the Pi Gateway. var importTask = new Task(() => { // wait a little so the browser can render and start listening to events Task.Delay(1000).Wait(); _hubContext.Clients.All.setButtonVisibilty(this.SignalRNotificationKey, false); foreach (var scheduledTransaction in scheduledTransactions) { System.Threading.Thread.Sleep(1000); UpdateProgressMessage(string.Format("Migrating Scheduled Transactions: {0} of {1}", scheduledTransactionProgress, scheduledTransactionCount), " "); scheduledTransactionProgress++; var nmiSubscriptionId = scheduledTransaction.GatewayScheduleId; var nmiCustomerId = scheduledTransaction.ForeignKey; var piCustomerId = subscriptionImportRecordLookup.GetValueOrNull(nmiSubscriptionId); if (piCustomerId == null) { scheduledTransactionResultsBuilder.AppendFormat( "WARNING: No Pi CustomerId found for Financial Scheduled Transaction with Id: {0} which is associated NMI SubscriptionId: '{1}'" + Environment.NewLine, scheduledTransaction.Id, nmiSubscriptionId ); continue; } // Pi requires that NextPaymentDate is in the Future (using UTC). That math is done in the gateway implementation... // if the NextPayment null or earlier than whatever Pi considers the earliest start date, see if we can fix that up by calling GetStatus if (scheduledTransaction.NextPaymentDate == null || scheduledTransaction.NextPaymentDate < earliestPiStartDate) { financialScheduledTransactionService.GetStatus(scheduledTransaction, out errorMessage); } if (scheduledTransaction.NextPaymentDate == null) { // Shouldn't happen, but just in case scheduledTransactionResultsBuilder.AppendFormat( "WARNING: Unknown NextPaymentDate for FinancialScheduledTransaction.Id: {0} NMI SubscriptionId: '{1}'" + Environment.NewLine, scheduledTransaction.Id, nmiSubscriptionId ); continue; } if (scheduledTransaction.NextPaymentDate < earliestPiStartDate) { if ((scheduledTransaction.NextPaymentDate > RockDateTime.Today) && earliestPiStartDate.Subtract(scheduledTransaction.NextPaymentDate.Value).TotalDays <= 2) { // if the NextPaymentDate is after Today but before the Earliest Pi Start Date, it'll be off by less than 24 hrs, so just reschedule it for the Earliest Pi Start Date scheduledTransaction.NextPaymentDate = earliestPiStartDate; } else { // if the NextPaymentDate is still too early AFTER getting the most recent status, then we can't safely figure it out, so report it scheduledTransactionResultsBuilder.AppendFormat( "WARNING: NextPaymentDate of {0} for FinancialScheduledTransaction.Id: {1} and NMI SubscriptionId: '{2}' must have a NextPaymentDate of at least {3}." + Environment.NewLine, scheduledTransaction.NextPaymentDate, scheduledTransaction.Id, nmiSubscriptionId, earliestPiStartDate ); } } // create a subscription in the Pi System, then cancel the one on the NMI system PaymentSchedule paymentSchedule = new PaymentSchedule { TransactionFrequencyValue = DefinedValueCache.Get(scheduledTransaction.TransactionFrequencyValueId), StartDate = scheduledTransaction.NextPaymentDate.Value, PersonId = scheduledTransaction.AuthorizedPersonAlias.PersonId }; ReferencePaymentInfo referencePaymentInfo = new ReferencePaymentInfo { GatewayPersonIdentifier = piCustomerId, Description = string.Format("Migrated from NMI SubscriptionID:{0}", nmiSubscriptionId) }; var piGateway = (piGatewayComponent as PiGateway); string alreadyMigratedPiSubscriptionId = null; if (piGateway != null) { var customerPiSubscriptions = piGateway.SearchCustomerSubscriptions(piFinancialGateway, piCustomerId); alreadyMigratedPiSubscriptionId = customerPiSubscriptions.Data.Where(a => a.Description.Contains(referencePaymentInfo.Description)).Select(a => a.Customer.Id).FirstOrDefault(); } if (string.IsNullOrEmpty(alreadyMigratedPiSubscriptionId)) { // hasn't already been migrated, so go ahead and migrate it var tempFinancialScheduledTransaction = piGatewayComponent.AddScheduledPayment(piFinancialGateway, paymentSchedule, referencePaymentInfo, out errorMessage); if (tempFinancialScheduledTransaction != null) { ////////////#### DISABLE this when debugger ##### nmiGatewayComponent.CancelScheduledPayment(scheduledTransaction, out errorMessage); // update the scheduled transaction to point to the Pi scheduled transaction using (var updateRockContext = new RockContext()) { // Attach the person to the updateRockContext so that it'll be tracked/saved using updateRockContext updateRockContext.FinancialScheduledTransactions.Attach(scheduledTransaction); scheduledTransaction.TransactionCode = tempFinancialScheduledTransaction.TransactionCode; scheduledTransaction.GatewayScheduleId = tempFinancialScheduledTransaction.GatewayScheduleId; scheduledTransaction.FinancialGatewayId = tempFinancialScheduledTransaction.FinancialGatewayId; updateRockContext.SaveChanges(); } scheduledTransactionResultsBuilder.AppendFormat( "SUCCESS: Scheduled Transaction migration succeeded. (FinancialScheduledTransaction.Id: {0}, NMI SubscriptionId: '{1}', Pi CustomerId: {2}, Pi SubscriptionId: {3})" + Environment.NewLine, scheduledTransaction.Id, nmiSubscriptionId, piCustomerId, scheduledTransaction.GatewayScheduleId ); } else { scheduledTransactionResultsBuilder.AppendFormat( "ERROR: Scheduled Transaction migration failed. ErrorMessage: {0}, FinancialScheduledTransaction.Id: {1}, NMI SubscriptionId: '{2}', Pi CustomerId: {3}" + Environment.NewLine, errorMessage, scheduledTransaction.Id, nmiSubscriptionId, piCustomerId ); } } else { scheduledTransactionResultsBuilder.AppendFormat( "INFO: Scheduled Transaction already migrated to PI. FinancialScheduledTransaction.Id: {0}, NMI SubscriptionId: '{1}', Pi SubscriptionId: '{2}', Pi CustomerId: {3}" + Environment.NewLine, scheduledTransaction.Id, nmiSubscriptionId, alreadyMigratedPiSubscriptionId, piCustomerId ); } } }); string importResult = string.Empty; importTask.ContinueWith((c) => { if (c.Exception != null) { ExceptionLogService.LogException(c.Exception); scheduledTransactionResultsBuilder.AppendLine(string.Format("EXCEPTION: {0}", c.Exception.Flatten().Message)); importResult = "EXCEPTION"; UpdateProgressMessage(importResult, scheduledTransactionResultsBuilder.ToString()); } else { importResult = "Migrate Scheduled Transactions Completed Successfully"; UpdateProgressMessage(importResult, scheduledTransactionResultsBuilder.ToString()); } this.SetBlockUserPreference("MigrateScheduledTransactionsResultSummary", importResult); this.SetBlockUserPreference("MigrateScheduledTransactionsResultDetails", scheduledTransactionResultsBuilder.ToString()); }); importTask.Start(); nbMigrateScheduledTransactions.Visible = false; // wait for 5 seconds to see if this happens fast enough to do without Signal R. Otherwise, let the importTask continue and send progress to Signal R. var waitResult = importTask.Wait(5000); if (waitResult) { // wait just a little bit to make sure the importResult gets set System.Threading.Thread.Sleep(1000); nbMigrateScheduledTransactions.Visible = true; nbMigrateScheduledTransactions.Title = "Success"; nbMigrateScheduledTransactions.NotificationBoxType = Rock.Web.UI.Controls.NotificationBoxType.Success; var resultDetails = scheduledTransactionResultsBuilder.ToString(); if (resultDetails.Contains("ERROR") || resultDetails.Contains("WARNING")) { nbMigrateScheduledTransactions.Title = "Completed with Warnings"; nbMigrateScheduledTransactions.NotificationBoxType = Rock.Web.UI.Controls.NotificationBoxType.Info; } nbMigrateScheduledTransactions.Text = importResult; nbMigrateScheduledTransactions.Details = resultDetails.ConvertCrLfToHtmlBr(); } }
/// <summary> /// Handles the ItemDataBound event of the rptSavedAccounts control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="RepeaterItemEventArgs"/> instance containing the event data.</param> protected void rptSavedAccounts_ItemDataBound(object sender, RepeaterItemEventArgs e) { var financialPersonSavedAccount = e.Item.DataItem as FinancialPersonSavedAccount; if (financialPersonSavedAccount == null) { return; } var lSavedAccountName = e.Item.FindControl("lSavedAccountName") as Literal; var pnlCreditCardInfo = e.Item.FindControl("pnlSavedAccountCreditCardInfo") as Panel; var lOtherCurrencyTypeInfo = e.Item.FindControl("lSavedAccountOtherCurrencyTypeInfo") as Literal; var lSavedAccountCardTypeLast4 = e.Item.FindControl("lSavedAccountCardTypeLast4") as Literal; var lSavedAccountExpiration = e.Item.FindControl("lSavedAccountExpiration") as Literal; var lSavedAccountInUseStatusHtml = e.Item.FindControl("lSavedAccountInUseStatusHtml") as Literal; var btnSavedAccountDelete = e.Item.FindControl("btnSavedAccountDelete") as LinkButton; lSavedAccountName.Text = financialPersonSavedAccount.Name; var financialPaymentDetail = financialPersonSavedAccount.FinancialPaymentDetail; string creditCardType = null; string accountNumberMasked = financialPaymentDetail?.AccountNumberMasked; if (financialPaymentDetail?.CreditCardTypeValueId != null) { creditCardType = DefinedValueCache.GetValue(financialPaymentDetail.CreditCardTypeValueId.Value); } var currencyTypeIdCreditCard = DefinedValueCache.GetId(Rock.SystemGuid.DefinedValue.CURRENCY_TYPE_CREDIT_CARD.AsGuid()); if (financialPaymentDetail?.CurrencyTypeValueId == currencyTypeIdCreditCard) { pnlCreditCardInfo.Visible = true; lOtherCurrencyTypeInfo.Visible = false; if (accountNumberMasked.IsNotNullOrWhiteSpace() && accountNumberMasked.Length >= 4) { var last4 = accountNumberMasked.Substring(accountNumberMasked.Length - 4); lSavedAccountCardTypeLast4.Text = $"{creditCardType} - {last4}"; } else { lSavedAccountCardTypeLast4.Text = creditCardType; } lSavedAccountExpiration.Text = $"Exp: {financialPaymentDetail.ExpirationDate}"; } else { pnlCreditCardInfo.Visible = false; lOtherCurrencyTypeInfo.Visible = true; lOtherCurrencyTypeInfo.Text = financialPaymentDetail?.CurrencyTypeValue?.Value; } var cardIsExpired = financialPaymentDetail.CardExpirationDate.HasValue && financialPaymentDetail.CardExpirationDate.Value < RockDateTime.Now; var cardInUse = new FinancialScheduledTransactionService(new RockContext()).Queryable().Where(a => a.FinancialPaymentDetailId.HasValue && a.IsActive && a.FinancialPaymentDetail.FinancialPersonSavedAccountId.HasValue && a.FinancialPaymentDetail.FinancialPersonSavedAccountId.Value == financialPersonSavedAccount.Id).Any(); btnSavedAccountDelete.Visible = !cardInUse; btnSavedAccountDelete.CommandArgument = financialPersonSavedAccount.Guid.ToString(); if (cardIsExpired) { lSavedAccountInUseStatusHtml.Text = "<span class='text-xs text-danger text-nowrap'>Expired</span>"; } else { if (cardInUse) { lSavedAccountInUseStatusHtml.Text = "<span class='text-xs text-success text-nowrap'>In Use</span>"; } else { lSavedAccountInUseStatusHtml.Text = "<span class='text-xs text-muted text-nowrap'>Not In Use</span>"; } } }
/// <summary> /// Sends the expired credit card notices. /// </summary> /// <param name="context">The context.</param> /// <returns></returns> /// <exception cref="Exception">Expiring credit card email is missing.</exception> private SendExpiredCreditCardNoticesResult SendExpiredCreditCardNotices(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; var rockContext = new RockContext(); // Get the details for the email that we'll be sending out. Guid?systemEmailGuid = dataMap.GetString(AttributeKey.ExpiringCreditCardEmail).AsGuidOrNull(); SystemCommunication systemCommunication = null; if (systemEmailGuid.HasValue) { var systemCommunicationService = new SystemCommunicationService(rockContext); systemCommunication = systemCommunicationService.Get(systemEmailGuid.Value); } if (systemCommunication == null) { throw new Exception("Expiring credit card email is missing."); } // Fetch the configured Workflow once if one was set, we'll use it later. Guid?workflowGuid = dataMap.GetString(AttributeKey.Workflow).AsGuidOrNull(); WorkflowTypeCache workflowType = null; WorkflowService workflowService = new WorkflowService(rockContext); if (workflowGuid != null) { workflowType = WorkflowTypeCache.Get(workflowGuid.Value); } var financialScheduledTransactionQuery = new FinancialScheduledTransactionService(rockContext).Queryable() .Where(t => t.IsActive && t.FinancialPaymentDetail.ExpirationMonthEncrypted != null && (t.EndDate == null || t.EndDate > DateTime.Now)) .AsNoTracking(); List <ScheduledTransactionInfo> scheduledTransactionInfoList = financialScheduledTransactionQuery.Select(a => new ScheduledTransactionInfo { Id = a.Id, FinancialPaymentDetail = a.FinancialPaymentDetail, AuthorizedPersonAliasGuid = a.AuthorizedPersonAlias.Guid, Person = a.AuthorizedPersonAlias.Person }).ToList(); // Get the current month and year DateTime now = DateTime.Now; int currentMonth = now.Month; int currentYYYY = now.Year; // get the common merge fields once, so we don't have to keep calling it for every person, then create a new mergeFields for each person, starting with a copy of the common merge fields var commonMergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields(null); var result = new SendExpiredCreditCardNoticesResult { ExaminedCount = scheduledTransactionInfoList.Count() }; foreach (ScheduledTransactionInfo scheduledTransactionInfo in scheduledTransactionInfoList.OrderByDescending(a => a.Id)) { int?expirationMonth = scheduledTransactionInfo.FinancialPaymentDetail.ExpirationMonth; int?expirationYYYY = scheduledTransactionInfo.FinancialPaymentDetail.ExpirationYear; if (!expirationMonth.HasValue || !expirationMonth.HasValue) { continue; } int warningYear = expirationYYYY.Value; int warningMonth = expirationMonth.Value - 1; if (warningMonth == 0) { warningYear -= 1; warningMonth = 12; } if ((warningYear == currentYYYY) && (warningMonth == currentMonth)) { string maskedCardNumber = string.Empty; if (!string.IsNullOrEmpty(scheduledTransactionInfo.FinancialPaymentDetail.AccountNumberMasked) && scheduledTransactionInfo.FinancialPaymentDetail.AccountNumberMasked.Length >= 4) { maskedCardNumber = scheduledTransactionInfo.FinancialPaymentDetail.AccountNumberMasked.Substring(scheduledTransactionInfo.FinancialPaymentDetail.AccountNumberMasked.Length - 4); } string expirationDateMMYYFormatted = scheduledTransactionInfo.FinancialPaymentDetail?.ExpirationDate; var recipients = new List <RockEmailMessageRecipient>(); var person = scheduledTransactionInfo.Person; if (!person.IsEmailActive || person.Email.IsNullOrWhiteSpace() || person.EmailPreference == EmailPreference.DoNotEmail) { continue; } // make a mergeFields for this person, starting with copy of the commonFieldFields var mergeFields = new Dictionary <string, object>(commonMergeFields); mergeFields.Add("Person", person); mergeFields.Add("Card", maskedCardNumber); mergeFields.Add("Expiring", expirationDateMMYYFormatted); recipients.Add(new RockEmailMessageRecipient(person, mergeFields)); var emailMessage = new RockEmailMessage(systemCommunication); emailMessage.SetRecipients(recipients); emailMessage.Send(out List <string> emailErrors); if (emailErrors.Any()) { var errorLines = new StringBuilder(); errorLines.AppendLine(string.Empty); foreach (string error in emailErrors) { errorLines.AppendLine(error); } // Provide better identifying context in case the errors are too vague. var exception = new Exception($"Unable to send email (Person ID = {person.Id}).{errorLines}"); result.EmailSendExceptions.Add(exception); } else { result.NoticesSentCount++; } // Start workflow for this person if (workflowType != null) { Dictionary <string, string> attributes = new Dictionary <string, string>(); attributes.Add("Person", scheduledTransactionInfo.AuthorizedPersonAliasGuid.ToString()); attributes.Add("Card", maskedCardNumber); attributes.Add("Expiring", expirationDateMMYYFormatted); attributes.Add("FinancialScheduledTransactionId", scheduledTransactionInfo.Id.ToString()); StartWorkflow(workflowService, workflowType, attributes, $"{person.FullName} (scheduled transaction Id: {scheduledTransactionInfo.Id})"); } } } return(result); }
/// <summary> /// Binds the scheduled transactions. /// </summary> private void BindScheduledTransactions() { var rockContext = new RockContext(); var financialScheduledTransactionService = new FinancialScheduledTransactionService(rockContext); var qry = financialScheduledTransactionService .Queryable("ScheduledTransactionDetails,FinancialPaymentDetail.CurrencyTypeValue,FinancialPaymentDetail.CreditCardTypeValue") .AsNoTracking(); // Valid Accounts var accountGuids = GetAttributeValue(AttributeKey.Accounts).SplitDelimitedValues().AsGuidList(); if (accountGuids.Any()) { qry = qry.Where(t => t.ScheduledTransactionDetails.Any(d => accountGuids.Contains(d.Account.Guid))); } if (Person.GivingGroupId.HasValue) { // Person contributes with family qry = qry.Where(t => t.AuthorizedPersonAlias.Person.GivingGroupId == Person.GivingGroupId); } else { // Person contributes individually qry = qry.Where(t => t.AuthorizedPersonAlias.PersonId == Person.Id); } // only show the button if there some in active scheduled transactions btnShowInactiveScheduledTransactions.Visible = qry.Any(a => !a.IsActive); var includeInactive = hfShowInactiveScheduledTransactions.Value.AsBoolean(); if (!includeInactive) { btnShowInactiveScheduledTransactions.Text = "Show Inactive"; qry = qry.Where(t => t.IsActive); } else { // if including Inactive, show both Active and Inactive btnShowInactiveScheduledTransactions.Text = "Hide Inactive"; } qry = qry .OrderBy(t => t.AuthorizedPersonAlias.Person.LastName) .ThenBy(t => t.AuthorizedPersonAlias.Person.NickName) .ThenByDescending(t => t.IsActive) .ThenByDescending(t => t.StartDate); var scheduledTransactionList = qry.ToList(); foreach (var schedule in scheduledTransactionList) { try { // This will ensure we have the most recent status, even if the schedule hasn't been making payments. string errorMessage; financialScheduledTransactionService.GetStatus(schedule, out errorMessage); } catch (Exception ex) { // log and ignore LogException(ex); } } rptScheduledTransaction.DataSource = scheduledTransactionList; rptScheduledTransaction.DataBind(); }
/// <summary> /// Binds the profile. /// </summary> /// <param name="profileId">The profile id.</param> protected void BindGivingProfile(int profileId) { DateTime currentDate = DateTimeOffset.Now.DateTime; var accountService = new FinancialAccountService(); var activeAccounts = accountService.Queryable().Where(f => f.IsActive && (f.StartDate == null || f.StartDate <= currentDate) && (f.EndDate == null || f.EndDate <= currentDate) && f.PublicName != null && f.PublicName != ""); var accountGuids = GetAttributeValues("DefaultAccounts").Select(Guid.Parse).ToList(); var scheduledTransactionService = new FinancialScheduledTransactionService(); var transactionList = new Dictionary <FinancialAccount, decimal>(); FinancialScheduledTransaction scheduledTransaction; if (profileId != 0 && scheduledTransactionService.TryGet(profileId, out scheduledTransaction)) { // Retrieve Transaction btnFrequency.SelectedValue = scheduledTransaction.TransactionFrequencyValue.Id.ToString(); divFrequency.Visible = true; dtpStartDate.SelectedDate = scheduledTransaction.StartDate; divRecurrence.Visible = true; divLimitGifts.Visible = true; if (scheduledTransaction.NumberOfPayments != null) { chkLimitGifts.Checked = true; txtLimitNumber.Text = scheduledTransaction.NumberOfPayments.ToString(); divLimitNumber.Visible = true; } foreach (var details in scheduledTransaction.ScheduledTransactionDetails) { transactionList.Add(details.Account, details.Amount); } } else { // New Transaction IQueryable <FinancialAccount> selectedAccounts = activeAccounts; if (accountGuids.Any()) { selectedAccounts = selectedAccounts.Where(a => accountGuids.Contains(a.Guid)); } var campusId = btnCampusList.SelectedValueAsInt(); selectedAccounts = selectedAccounts.Where(f => f.CampusId == campusId || (f.CampusId == null && f.ChildAccounts.Count() == 0)); foreach (var account in selectedAccounts) { transactionList.Add(account, 0M); } } if (activeAccounts.Count() > transactionList.Count() && Convert.ToBoolean(GetAttributeValue("AdditionalAccounts"))) { var unselectedAccounts = activeAccounts.Where(a => !accountGuids.Contains(a.Guid)).ToList(); if (unselectedAccounts.Any()) { btnAddAccount.DataTextField = "PublicName"; btnAddAccount.DataValueField = "Id"; btnAddAccount.DataSource = unselectedAccounts.ToList(); btnAddAccount.DataBind(); } } else { divAddAccount.Visible = false; } Session["CachedAmounts"] = transactionList; Session["CachedProfileId"] = profileId; rptAccountList.DataSource = transactionList; rptAccountList.DataBind(); spnTotal.InnerText = transactionList.Sum(d => d.Value).ToString("f2"); }
/// <summary> /// Handles the Click event of the btnGive 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 btnGive_Click(object sender, EventArgs e) { Person person = FindPerson(); using (new UnitOfWorkScope()) { RockTransactionScope.WrapTransaction(() => { var groupLocationService = new GroupLocationService(); var groupMemberService = new GroupMemberService(); var phoneService = new PhoneNumberService(); var locationService = new LocationService(); var groupService = new GroupService(); GroupLocation groupLocation; Location homeAddress; Group familyGroup; var homeLocationType = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.LOCATION_TYPE_HOME); var addressList = locationService.Queryable().Where(l => l.Street1 == txtStreet.Text && l.City == txtCity.Text && l.State == ddlState.SelectedValue && l.Zip == txtZip.Text && l.LocationTypeValueId == homeLocationType.Id).ToList(); if (!addressList.Any()) { homeAddress = new Location(); locationService.Add(homeAddress, person.Id); } else { homeAddress = addressList.FirstOrDefault(); } homeAddress.Street1 = txtStreet.Text ?? homeAddress.Street1; homeAddress.City = txtCity.Text ?? homeAddress.City; homeAddress.State = ddlState.SelectedValue ?? homeAddress.State; homeAddress.Zip = txtZip.Text ?? homeAddress.Zip; homeAddress.IsActive = true; homeAddress.IsLocation = true; homeAddress.Country = "US"; homeAddress.LocationTypeValueId = homeLocationType.Id; locationService.Save(homeAddress, person.Id); GroupType familyGroupType = new GroupTypeService().Get(new Guid(Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY)); var familyGroupList = groupMemberService.Queryable().Where(g => g.PersonId == person.Id && g.Group.GroupType.Guid == familyGroupType.Guid).Select(g => g.Group).ToList(); if (!familyGroupList.Any()) { familyGroup = new Group(); familyGroup.IsActive = true; familyGroup.IsSystem = false; familyGroup.IsSecurityRole = false; familyGroup.Name = "The " + txtLastName.Text + " Family"; familyGroup.GroupTypeId = familyGroupType.Id; groupService.Add(familyGroup, person.Id); groupService.Save(familyGroup, person.Id); var familyMember = new GroupMember(); familyMember.IsSystem = false; familyMember.GroupId = familyGroup.Id; familyMember.PersonId = person.Id; familyMember.GroupRoleId = new GroupRoleService().Get(new Guid(Rock.SystemGuid.GroupRole.GROUPROLE_FAMILY_MEMBER_ADULT)).Id; groupMemberService.Add(familyMember, person.Id); groupMemberService.Save(familyMember, person.Id); } else { familyGroup = familyGroupList.FirstOrDefault(); } var groupLocationList = groupLocationService.Queryable().Where(g => g.GroupLocationTypeValueId == familyGroupType.Id && g.GroupId == familyGroup.Id).ToList(); if (!groupLocationList.Any()) { groupLocation = new GroupLocation(); groupLocation.GroupId = familyGroup.Id; groupLocation.LocationId = homeAddress.Id; groupLocation.IsMailing = true; groupLocation.IsLocation = true; groupLocation.GroupLocationTypeValueId = homeLocationType.Id; groupLocationService.Add(groupLocation, person.Id); groupLocationService.Save(groupLocation, person.Id); } else { groupLocation = groupLocationList.FirstOrDefault(); } groupLocation.LocationId = homeAddress.Id; groupLocationService.Save(groupLocation, person.Id); var homePhoneType = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.PERSON_PHONE_TYPE_HOME); string phoneNumeric = txtPhone.Text.AsNumeric(); if (!phoneService.Queryable().Where(n => n.PersonId == person.Id && n.NumberTypeValueId == homePhoneType.Id && n.Number == phoneNumeric).Any()) { var homePhone = new PhoneNumber(); homePhone.Number = phoneNumeric; homePhone.PersonId = person.Id; homePhone.IsSystem = false; homePhone.IsMessagingEnabled = false; homePhone.IsUnlisted = false; homePhone.NumberTypeValueId = homePhoneType.Id; phoneService.Add(homePhone, person.Id); phoneService.Save(homePhone, person.Id); } }); } var amountList = (Dictionary <FinancialAccount, Decimal>)Session["CachedAmounts"]; var profileId = (int)Session["CachedProfileId"]; Location giftLocation = new Location(); var configValues = (Dictionary <string, object>)Session["CachedMergeFields"]; configValues.Add("Date", DateTimeOffset.Now.ToString("MM/dd/yyyy hh:mm tt")); var receiptTemplate = GetAttributeValue("ReceiptMessage"); lReceipt.Text = receiptTemplate.ResolveMergeFields(configValues); var summaryTemplate = GetAttributeValue("SummaryMessage"); string summaryMessage = summaryTemplate.ResolveMergeFields(configValues); // #TODO test card through gateway if (btnFrequency.SelectedIndex > -1 && btnFrequency.SelectedValueAsInt() > 0) { using (new UnitOfWorkScope()) { RockTransactionScope.WrapTransaction(() => { var scheduledTransactionDetailService = new FinancialScheduledTransactionDetailService(); var scheduledTransactionService = new FinancialScheduledTransactionService(); FinancialScheduledTransaction scheduledTransaction; var detailList = amountList.ToList(); if (profileId > 0) { scheduledTransaction = scheduledTransactionService.Get(profileId); } else { scheduledTransaction = new FinancialScheduledTransaction(); scheduledTransactionService.Add(scheduledTransaction, person.Id); } DateTime startDate = (DateTime)dtpStartDate.SelectedDate; if (startDate != null) { scheduledTransaction.StartDate = startDate; } scheduledTransaction.TransactionFrequencyValueId = (int)btnFrequency.SelectedValueAsInt(); scheduledTransaction.AuthorizedPersonId = person.Id; scheduledTransaction.IsActive = true; if (!string.IsNullOrEmpty(txtCreditCard.Text)) { scheduledTransaction.CardReminderDate = mypExpiration.SelectedDate; } if (chkLimitGifts.Checked && !string.IsNullOrWhiteSpace(txtLimitNumber.Text)) { scheduledTransaction.NumberOfPayments = Convert.ToInt32(txtLimitNumber.Text); } foreach (var detail in amountList.ToList()) { var scheduledTransactionDetail = new FinancialScheduledTransactionDetail(); scheduledTransactionDetail.AccountId = detail.Key.Id; scheduledTransactionDetail.Amount = detail.Value; scheduledTransactionDetail.ScheduledTransactionId = scheduledTransaction.Id; scheduledTransactionDetailService.Add(scheduledTransactionDetail, person.Id); scheduledTransactionDetailService.Save(scheduledTransactionDetail, person.Id); } // implement gateway charge() scheduledTransactionService.Save(scheduledTransaction, person.Id); }); } } else { using (new UnitOfWorkScope()) { RockTransactionScope.WrapTransaction(() => { var transactionService = new FinancialTransactionService(); var tdService = new FinancialTransactionDetailService(); var transaction = new FinancialTransaction(); var detailList = amountList.ToList(); transaction.Summary = summaryMessage; transaction.Amount = detailList.Sum(d => d.Value); transaction.TransactionTypeValueId = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.TRANSACTION_TYPE_CONTRIBUTION).Id; transaction.TransactionDateTime = DateTimeOffset.Now.DateTime; transaction.AuthorizedPersonId = person.Id; transactionService.Add(transaction, person.Id); foreach (var detail in detailList) { var td = new FinancialTransactionDetail(); td.TransactionId = transaction.Id; td.AccountId = detail.Key.Id; td.Amount = detail.Value; td.TransactionId = transaction.Id; tdService.Add(td, person.Id); tdService.Save(td, person.Id); } // #TODO implement gateway.charge() transactionService.Save(transaction, person.Id); }); } } Session["CachedMergeFields"] = configValues; pnlConfirm.Visible = false; pnlComplete.Visible = true; pnlContribution.Update(); }