public TransferPaidResponseDTO TransferPaid(DateTime?eventTimestamp, StripeTransfer transfer)
        {
            _logger.Debug("Processing transfer.paid event for transfer id " + transfer.Id);

            var response = new TransferPaidResponseDTO();

            // Don't process this transfer if we already have a deposit for the same transfer id
            var existingDeposit = _donationService.GetDepositByProcessorTransferId(transfer.Id);

            if (existingDeposit != null)
            {
                var msg = $"Deposit {existingDeposit.Id} already created for transfer {existingDeposit.ProcessorTransferId}";
                _logger.Debug(msg);
                response.TotalTransactionCount = 0;
                response.Message   = msg;
                response.Exception = new ApplicationException(msg);
                return(response);
            }

            // Don't process this transfer if we can't find any charges for the transfer
            var charges = _paymentProcessorService.GetChargesForTransfer(transfer.Id);

            if (charges == null || charges.Count <= 0)
            {
                var msg = "No charges found for transfer: " + transfer.Id;
                _logger.Debug(msg);
                response.TotalTransactionCount = 0;
                response.Message   = msg;
                response.Exception = new ApplicationException(msg);
                return(response);
            }

            var depositName = DateTime.Now.ToString(BatchNameDateFormat);

            var paymentcharges  = charges.Where(m => m.Metadata != null && m.Metadata.ContainsKey("crossroads_transaction_type") && m.Metadata["crossroads_transaction_type"].ToString() == "payment").ToList();
            var donationcharges = charges.Except(paymentcharges).ToList();

            if (paymentcharges.Count + donationcharges.Count != charges.Count)
            {
                var msg = "Donation and Payment charges count error for transfer: " + transfer.Id;
                _logger.Debug(msg);
                response.TotalTransactionCount = 0;
                response.Message   = msg;
                response.Exception = new ApplicationException(msg);
                return(response);
            }

            var paymentBatch  = CreateBatchDTOFromCharges(paymentcharges, depositName + "P", eventTimestamp, transfer, ref response);
            var donationBatch = CreateBatchDTOFromCharges(donationcharges, depositName + "D", eventTimestamp, transfer, ref response);

            var stripeTotalFees = paymentBatch.BatchFeeTotal + donationBatch.BatchFeeTotal;

            // Create the deposit
            var deposit = new DepositDTO
            {
                // Account number must be non-null, and non-empty; using a single space to fulfill this requirement
                AccountNumber   = " ",
                BatchCount      = paymentBatch.ItemCount > 0 && donationBatch.ItemCount > 0 ? 2 : 1,
                DepositDateTime = DateTime.Now,
                DepositName     = depositName,
                // This is the amount from Stripe - will show out of balance if does not match batch total above
                DepositTotalAmount = ((transfer.Amount / Constants.StripeDecimalConversionValue) + (stripeTotalFees / Constants.StripeDecimalConversionValue)),
                ProcessorFeeTotal  = stripeTotalFees / Constants.StripeDecimalConversionValue,
                DepositAmount      = transfer.Amount / Constants.StripeDecimalConversionValue,
                Exported           = false,
                Notes = null,
                ProcessorTransferId = transfer.Id
            };

            try
            {
                response.Deposit = _donationService.CreateDeposit(deposit);
            }
            catch (Exception e)
            {
                _logger.Error("Failed to create batch deposit", e);
                throw;
            }

            // Create the batch, with the above deposit id
            paymentBatch.DepositId  = response.Deposit.Id;
            donationBatch.DepositId = response.Deposit.Id;

            //donation Batch
            try
            {
                if (donationBatch.ItemCount > 0)
                {
                    response.Batch.Add(_donationService.CreateDonationBatch(donationBatch));
                }
            }
            catch (Exception e)
            {
                _logger.Error("Failed to create donation batch", e);
                throw;
            }

            // payment Batch
            try
            {
                if (paymentBatch.ItemCount > 0)
                {
                    response.Batch.Add(_paymentService.CreatePaymentBatch(paymentBatch));
                }
            }
            catch (Exception e)
            {
                _logger.Error("Failed to create payment batch", e);
                throw;
            }

            return(response);
        }
        private DonationBatchDTO CreateBatchDTOFromCharges(List <StripeCharge> charges, string batchName, DateTime?eventTimestamp, StripeTransfer transfer, ref TransferPaidResponseDTO response)
        {
            var now = DateTime.Now;

            var batch = new DonationBatchDTO()
            {
                BatchName           = batchName,
                SetupDateTime       = now,
                BatchTotalAmount    = 0,
                ItemCount           = 0,
                BatchEntryType      = _batchEntryTypePaymentProcessor,
                FinalizedDateTime   = now,
                DepositId           = null,
                ProcessorTransferId = transfer.Id
            };

            response.TotalTransactionCount += charges.Count;

            // Sort charges so we process refunds for payments in the same transfer after the actual payment is processed
            var sortedCharges = charges.OrderBy(charge => charge.Type);

            foreach (var charge in sortedCharges)
            {
                try
                {
                    var          paymentId = charge.Id;
                    StripeRefund refund    = null;
                    if ("refund".Equals(charge.Type)) // Credit Card Refund
                    {
                        refund    = _paymentProcessorService.GetChargeRefund(charge.Id);
                        paymentId = refund.Data[0].Id;
                    }
                    else if ("payment_refund".Equals(charge.Type)) // Bank Account Refund
                    {
                        var refundData = _paymentProcessorService.GetRefund(charge.Id);
                        refund = new StripeRefund
                        {
                            Data = new List <StripeRefundData>
                            {
                                refundData
                            }
                        };
                    }
                    //TODO: Pull these out into methods?
                    if (charge.Metadata != null && charge.Metadata.ContainsKey("crossroads_transaction_type") && charge.Metadata["crossroads_transaction_type"].ToString() == "payment")
                    {
                        PaymentDTO payment;
                        try
                        {
                            payment = _paymentService.GetPaymentByTransactionCode(paymentId);
                        }
                        catch (PaymentNotFoundException e)
                        {
                            payment = HandlePaymentNotFoundException(transfer, refund, paymentId, e, charge);
                        }

                        if (payment.BatchId != null)
                        {
                            var b = _paymentService.GetPaymentBatch(payment.BatchId.Value);
                            if (string.IsNullOrWhiteSpace(b.ProcessorTransferId))
                            {
                                // If this payment exists on another batch that does not have a Stripe transfer ID, we'll move it to our batch instead
                                var msg = $"Charge {charge.Id} already exists on batch {b.Id}, moving to new batch";
                                _logger.Debug(msg);
                            }
                            else
                            {
                                // If this payment exists on another batch that has a Stripe transfer ID, skip it
                                var msg = $"Charge {charge.Id} already exists on batch {b.Id} with transfer id {b.ProcessorTransferId}";
                                _logger.Debug(msg);
                                response.FailedUpdates.Add(new KeyValuePair <string, string>(charge.Id, msg));
                                continue;
                            }
                        }

                        if (payment.Status != DonationStatus.Declined && payment.Status != DonationStatus.Refunded)
                        {
                            _logger.Debug($"Updating charge id {charge.Id} to Deposited status");
                            _paymentService.UpdatePaymentStatus(payment.PaymentId, _donationStatusDeposited, eventTimestamp);
                        }
                        else
                        {
                            _logger.Debug($"Not updating charge id {charge.Id} to Deposited status - it was already {System.Enum.GetName(typeof(DonationStatus), payment.Status)}");
                        }
                        response.SuccessfulUpdates.Add(charge.Id);
                        batch.ItemCount++;
                        batch.BatchTotalAmount += (charge.Amount / Constants.StripeDecimalConversionValue);
                        batch.Payments.Add(new PaymentDTO {
                            PaymentId = payment.PaymentId, Amount = charge.Amount, ProcessorFee = charge.Fee, BatchId = payment.BatchId, ContactId = payment.ContactId, InvoiceId = payment.InvoiceId, StripeTransactionId = payment.StripeTransactionId
                        });
                        batch.BatchFeeTotal = batch.Payments.Sum(f => f.ProcessorFee);
                    }
                    else
                    {
                        DonationDTO donation;
                        try
                        {
                            donation = _donationService.GetDonationByProcessorPaymentId(paymentId);
                        }
                        catch (DonationNotFoundException e)
                        {
                            donation = HandleDonationNotFoundException(transfer, refund, paymentId, e, charge);
                        }

                        if (donation.BatchId != null)
                        {
                            var b = _donationService.GetDonationBatch(donation.BatchId.Value);
                            if (string.IsNullOrWhiteSpace(b.ProcessorTransferId))
                            {
                                // If this donation exists on another batch that does not have a Stripe transfer ID, we'll move it to our batch instead
                                var msg = $"Charge {charge.Id} already exists on batch {b.Id}, moving to new batch";
                                _logger.Debug(msg);
                            }
                            else
                            {
                                // If this donation exists on another batch that has a Stripe transfer ID, skip it
                                var msg = $"Charge {charge.Id} already exists on batch {b.Id} with transfer id {b.ProcessorTransferId}";
                                _logger.Debug(msg);
                                response.FailedUpdates.Add(new KeyValuePair <string, string>(charge.Id, msg));
                                continue;
                            }
                        }

                        if (donation.Status != DonationStatus.Declined && donation.Status != DonationStatus.Refunded)
                        {
                            _logger.Debug($"Updating charge id {charge.Id} to Deposited status");
                            _donationService.UpdateDonationStatus(int.Parse(donation.Id), _donationStatusDeposited, eventTimestamp);
                        }
                        else
                        {
                            _logger.Debug($"Not updating charge id {charge.Id} to Deposited status - it was already {System.Enum.GetName(typeof(DonationStatus), donation.Status)}");
                        }
                        response.SuccessfulUpdates.Add(charge.Id);
                        batch.ItemCount++;
                        batch.BatchTotalAmount += (charge.Amount / Constants.StripeDecimalConversionValue);
                        batch.Donations.Add(new DonationDTO {
                            Id = donation.Id, Amount = charge.Amount, Fee = charge.Fee
                        });
                        batch.BatchFeeTotal = batch.Donations.Sum(f => f.Fee);
                    }
                }
                catch (Exception e)
                {
                    _logger.Warn("Error updating charge " + charge, e);
                    response.FailedUpdates.Add(new KeyValuePair <string, string>(charge.Id, e.Message));
                }
            }

            if (response.FailedUpdates.Count > 0)
            {
                response.Exception = new ApplicationException("Could not update all charges to 'deposited' status, see message for details");
            }



            return(batch);
        }