/// <summary>
/// Processes the payments.
/// </summary>
/// <param name="gateway">The gateway.</param>
/// <param name="batchNamePrefix">The batch name prefix.</param>
/// <param name="payments">The payments.</param>
/// <param name="batchUrlFormat">The batch URL format.</param>
/// <param name="receiptEmail">The receipt email.</param>
/// <returns></returns>
public static string ProcessPayments( FinancialGateway gateway, string batchNamePrefix, List<Payment> payments, string batchUrlFormat = "", Guid? receiptEmail = null )
{
int totalPayments = 0;
int totalAlreadyDownloaded = 0;
int totalNoScheduledTransaction = 0;
int totalAdded = 0;
int totalReversals = 0;
int totalStatusChanges = 0;
var batches = new List<FinancialBatch>();
var batchSummary = new Dictionary<Guid, List<Decimal>>();
var initialControlAmounts = new Dictionary<Guid, decimal>();
var txnPersonNames = new Dictionary<Guid, string>();
var gatewayComponent = gateway.GetGatewayComponent();
var newTransactions = new List<FinancialTransaction>();
using ( var rockContext = new RockContext() )
{
var accountService = new FinancialAccountService( rockContext );
var txnService = new FinancialTransactionService( rockContext );
var batchService = new FinancialBatchService( rockContext );
var scheduledTxnService = new FinancialScheduledTransactionService( rockContext );
var contributionTxnType = DefinedValueCache.Read( Rock.SystemGuid.DefinedValue.TRANSACTION_TYPE_CONTRIBUTION.AsGuid() );
var defaultAccount = accountService.Queryable()
.Where( a =>
a.IsActive &&
!a.ParentAccountId.HasValue &&
( !a.StartDate.HasValue || a.StartDate.Value <= RockDateTime.Now ) &&
( !a.EndDate.HasValue || a.EndDate.Value >= RockDateTime.Now )
)
.OrderBy( a => a.Order )
.FirstOrDefault();
var batchTxnChanges = new Dictionary<Guid, List<string>>();
var batchBatchChanges = new Dictionary<Guid, List<string>>();
var scheduledTransactionIds = new List<int>();
foreach ( var payment in payments.Where( p => p.Amount > 0.0M ) )
{
totalPayments++;
var scheduledTransaction = scheduledTxnService.GetByScheduleId( payment.GatewayScheduleId );
if ( scheduledTransaction != null )
{
// Find existing payments with same transaction code
var txns = txnService
.Queryable( "TransactionDetails" )
.Where( t => t.TransactionCode == payment.TransactionCode )
.ToList();
// Calculate whether a transaction needs to be added
var txnAmount = CalculateTransactionAmount( payment, txns );
if ( txnAmount != 0.0M )
{
scheduledTransactionIds.Add( scheduledTransaction.Id );
if ( payment.ScheduleActive.HasValue )
{
scheduledTransaction.IsActive = payment.ScheduleActive.Value;
}
var transaction = new FinancialTransaction();
transaction.FinancialPaymentDetail = new FinancialPaymentDetail();
transaction.Guid = Guid.NewGuid();
transaction.TransactionCode = payment.TransactionCode;
transaction.TransactionDateTime = payment.TransactionDateTime;
transaction.Status = payment.Status;
transaction.StatusMessage = payment.StatusMessage;
transaction.ScheduledTransactionId = scheduledTransaction.Id;
transaction.AuthorizedPersonAliasId = scheduledTransaction.AuthorizedPersonAliasId;
transaction.SourceTypeValueId = scheduledTransaction.SourceTypeValueId;
txnPersonNames.Add( transaction.Guid, scheduledTransaction.AuthorizedPersonAlias.Person.FullName );
transaction.FinancialGatewayId = gateway.Id;
transaction.TransactionTypeValueId = contributionTxnType.Id;
if ( txnAmount < 0.0M )
{
transaction.Summary = "Reversal for previous transaction that failed during processing." + Environment.NewLine;
}
var currencyTypeValue = payment.CurrencyTypeValue;
var creditCardTypevalue = payment.CreditCardTypeValue;
if ( scheduledTransaction.FinancialPaymentDetail != null )
{
if ( currencyTypeValue == null && scheduledTransaction.FinancialPaymentDetail.CurrencyTypeValueId.HasValue )
{
currencyTypeValue = DefinedValueCache.Read( scheduledTransaction.FinancialPaymentDetail.CurrencyTypeValueId.Value );
}
if ( creditCardTypevalue == null && scheduledTransaction.FinancialPaymentDetail.CreditCardTypeValueId.HasValue )
{
creditCardTypevalue = DefinedValueCache.Read( scheduledTransaction.FinancialPaymentDetail.CreditCardTypeValueId.Value );
}
transaction.FinancialPaymentDetail.AccountNumberMasked = scheduledTransaction.FinancialPaymentDetail.AccountNumberMasked;
transaction.FinancialPaymentDetail.NameOnCardEncrypted = scheduledTransaction.FinancialPaymentDetail.NameOnCardEncrypted;
transaction.FinancialPaymentDetail.ExpirationMonthEncrypted = scheduledTransaction.FinancialPaymentDetail.ExpirationMonthEncrypted;
transaction.FinancialPaymentDetail.ExpirationYearEncrypted = scheduledTransaction.FinancialPaymentDetail.ExpirationYearEncrypted;
transaction.FinancialPaymentDetail.BillingLocationId = scheduledTransaction.FinancialPaymentDetail.BillingLocationId;
}
if ( currencyTypeValue != null )
{
transaction.FinancialPaymentDetail.CurrencyTypeValueId = currencyTypeValue.Id;
}
if ( creditCardTypevalue != null )
{
transaction.FinancialPaymentDetail.CreditCardTypeValueId = creditCardTypevalue.Id;
}
// Try to allocate the amount of the transaction based on the current scheduled transaction accounts
decimal remainingAmount = Math.Abs( txnAmount );
foreach ( var detail in scheduledTransaction.ScheduledTransactionDetails.Where( d => d.Amount != 0.0M ) )
{
var transactionDetail = new FinancialTransactionDetail();
transactionDetail.AccountId = detail.AccountId;
if ( detail.Amount <= remainingAmount )
{
// If the configured amount for this account is less than or equal to the remaining
// amount, allocate the configured amount
transactionDetail.Amount = detail.Amount;
remainingAmount -= detail.Amount;
}
else
{
// If the configured amount is greater than the remaining amount, only allocate
// the remaining amount
transaction.Summary += "Note: Downloaded transaction amount was less than the configured allocation amounts for the Scheduled Transaction.";
transactionDetail.Amount = remainingAmount;
transactionDetail.Summary = "Note: The downloaded amount was not enough to apply the configured amount to this account.";
remainingAmount = 0.0M;
}
transaction.TransactionDetails.Add( transactionDetail );
if ( remainingAmount <= 0.0M )
{
// If there's no amount left, break out of details
break;
}
}
// If there's still amount left after allocating based on current config, add the remainder
// to the account that was configured for the most amount
if ( remainingAmount > 0.0M )
{
transaction.Summary += "Note: Downloaded transaction amount was greater than the configured allocation amounts for the Scheduled Transaction.";
var transactionDetail = transaction.TransactionDetails
.OrderByDescending( d => d.Amount )
.FirstOrDefault();
if ( transactionDetail == null && defaultAccount != null )
{
transactionDetail = new FinancialTransactionDetail();
transactionDetail.AccountId = defaultAccount.Id;
transaction.TransactionDetails.Add( transactionDetail );
}
if ( transactionDetail != null )
{
transactionDetail.Amount += remainingAmount;
transactionDetail.Summary = "Note: Extra amount was applied to this account.";
}
}
// If the amount to apply was negative, update all details to be negative (absolute value was used when allocating to accounts)
if ( txnAmount < 0.0M )
{
foreach ( var txnDetail in transaction.TransactionDetails )
{
txnDetail.Amount = 0 - txnDetail.Amount;
}
}
// Get the batch
var batch = batchService.Get(
batchNamePrefix,
currencyTypeValue,
creditCardTypevalue,
transaction.TransactionDateTime.Value,
gateway.GetBatchTimeOffset(),
batches );
var batchChanges = new List<string>();
if ( batch.Id != 0 )
{
initialControlAmounts.AddOrIgnore( batch.Guid, batch.ControlAmount );
}
batch.ControlAmount += transaction.TotalAmount;
batch.Transactions.Add( transaction );
if ( txnAmount > 0.0M && receiptEmail.HasValue )
{
newTransactions.Add( transaction );
}
// Add summary
if ( !batchSummary.ContainsKey( batch.Guid ) )
{
batchSummary.Add( batch.Guid, new List<Decimal>() );
}
batchSummary[batch.Guid].Add( txnAmount );
if ( txnAmount > 0.0M )
{
totalAdded++;
}
else
{
totalReversals++;
}
}
else
{
totalAlreadyDownloaded++;
foreach ( var txn in txns.Where( t => t.Status != payment.Status || t.StatusMessage != payment.StatusMessage ) )
{
txn.Status = payment.Status;
txn.StatusMessage = payment.StatusMessage;
totalStatusChanges++;
}
}
}
else
{
totalNoScheduledTransaction++;
}
}
rockContext.SaveChanges();
// Queue a transaction to update the status of all affected scheduled transactions
var updatePaymentStatusTxn = new Rock.Transactions.UpdatePaymentStatusTransaction( gateway.Id, scheduledTransactionIds );
Rock.Transactions.RockQueue.TransactionQueue.Enqueue( updatePaymentStatusTxn );
if ( receiptEmail.HasValue )
{
// Queue a transaction to send receipts
var newTransactionIds = newTransactions.Select( t => t.Id ).ToList();
var sendPaymentReceiptsTxn = new Rock.Transactions.SendPaymentReceipts( receiptEmail.Value, newTransactionIds );
Rock.Transactions.RockQueue.TransactionQueue.Enqueue( sendPaymentReceiptsTxn );
}
}
StringBuilder sb = new StringBuilder();
sb.AppendFormat( "<li>{0} {1} downloaded.</li>", totalPayments.ToString( "N0" ),
( totalPayments == 1 ? "payment" : "payments" ) );
if ( totalAlreadyDownloaded > 0 )
{
sb.AppendFormat( "<li>{0} {1} previously downloaded and {2} already been added.</li>", totalAlreadyDownloaded.ToString( "N0" ),
( totalAlreadyDownloaded == 1 ? "payment was" : "payments were" ),
( totalAlreadyDownloaded == 1 ? "has" : "have" ) );
}
if ( totalStatusChanges > 0 )
{
sb.AppendFormat( "<li>{0} {1} previously downloaded but had a change of status.</li>", totalStatusChanges.ToString( "N0" ),
( totalStatusChanges == 1 ? "payment was" : "payments were" ) );
}
if ( totalNoScheduledTransaction > 0 )
{
sb.AppendFormat( "<li>{0} {1} could not be matched to an existing scheduled payment profile.</li>", totalNoScheduledTransaction.ToString( "N0" ),
( totalNoScheduledTransaction == 1 ? "payment" : "payments" ) );
}
sb.AppendFormat( "<li>{0} {1} successfully added.</li>", totalAdded.ToString( "N0" ),
( totalAdded == 1 ? "payment was" : "payments were" ) );
if ( totalReversals > 0 )
{
sb.AppendFormat( "<li>{0} {1} added as a reversal to a previous transaction.</li>", totalReversals.ToString( "N0" ),
( totalReversals == 1 ? "payment was" : "payments were" ) );
}
if ( totalStatusChanges > 0 )
{
sb.AppendFormat( "<li>{0} {1} previously downloaded but had a change of status.</li>", totalStatusChanges.ToString( "N0" ),
( totalStatusChanges == 1 ? "payment was" : "payments were" ) );
}
foreach ( var batchItem in batchSummary )
{
int items = batchItem.Value.Count;
if ( items > 0 )
{
var batch = batches
.Where( b => b.Guid.Equals( batchItem.Key ) )
.FirstOrDefault();
string batchName = string.Format( "'{0} ({1})'", batch.Name, batch.BatchStartDateTime.Value.ToString( "d" ) );
if ( !string.IsNullOrWhiteSpace( batchUrlFormat ) )
{
batchName = string.Format( "<a href='{0}'>{1}</a>", string.Format( batchUrlFormat, batch.Id ), batchName );
}
decimal sum = batchItem.Value.Sum();
string summaryformat = items == 1 ?
"<li>{0} transaction of {1} was added to the {2} batch.</li>" :
"<li>{0} transactions totaling {1} were added to the {2} batch</li>";
sb.AppendFormat( summaryformat, items.ToString( "N0" ), sum.FormatAsCurrency(), batchName );
}
}
return sb.ToString();
}