private PaymentDTO HandlePaymentNotFoundException(StripeTransfer transfer, StripeRefund refund, string paymentId, PaymentNotFoundException e, StripeCharge charge)
        {
            PaymentDTO payment;

            if (refund != null)
            {
                _logger.Warn($"Payment not found for refund {paymentId} on transfer {transfer.Id} - may be a refund due to a bank account error");
                // If this is a refund that doesn't exist in MP, create it, assuming it is a refund due to a bank account error (NSF, etc)
                if (_paymentService.CreatePaymentForBankAccountErrorRefund(refund) != null)
                {
                    payment        = _paymentService.GetPaymentByTransactionCode(paymentId);
                    payment.Status = DonationStatus.Declined; // make sure to set the correct status.
                    _logger.Debug($"Updating charge id {charge.Id} to Declined status");
                    _paymentService.UpdatePaymentStatus(payment.PaymentId, _donationStatusDeclined, refund.Data[0].BalanceTransaction.Created);
                }
                else
                {
                    _logger.Error($"Payment not found for refund {paymentId} on transfer {transfer.Id}, probably not a bank account error", e);
                    // ReSharper disable once PossibleIntendedRethrow
                    throw e;
                }
            }
            else
            {
                _logger.Warn($"Payment not found for charge {paymentId} on transfer {transfer.Id} - may be an ACH recurring gift that has not yet processed");

                _logger.Error($"Donation not found for charge {charge.Id} on transfer {transfer.Id}, charge does not appear to be related to an ACH recurring gift");
                // ReSharper disable once PossibleIntendedRethrow
                throw e;
            }
            return(payment);
        }
        private DonationDTO HandleDonationNotFoundException(StripeTransfer transfer, StripeRefund refund, string paymentId, DonationNotFoundException e, StripeCharge charge)
        {
            DonationDTO donation;

            if (refund != null)
            {
                _logger.Warn($"Payment not found for refund {paymentId} on transfer {transfer.Id} - may be a refund due to a bank account error");
                // If this is a refund that doesn't exist in MP, create it, assuming it is a refund due to a bank account error (NSF, etc)
                if (_donationService.CreateDonationForBankAccountErrorRefund(refund) != null)
                {
                    donation = _donationService.GetDonationByProcessorPaymentId(paymentId);
                    _logger.Debug($"Updating charge id {charge.Id} to Declined status");
                    _donationService.UpdateDonationStatus(refund.Data[0].ChargeId, _donationStatusDeclined, refund.Data[0].BalanceTransaction.Created);
                }
                else
                {
                    _logger.Error($"Payment not found for refund {paymentId} on transfer {transfer.Id}, probably not a bank account error", e);
                    // ReSharper disable once PossibleIntendedRethrow
                    throw e;
                }
            }
            else
            {
                _logger.Warn($"Payment not found for charge {paymentId} on transfer {transfer.Id} - may be an ACH recurring gift that has not yet processed");
                var stripeCharge = _paymentProcessorService.GetCharge(charge.Id);
                if (stripeCharge?.Source != null && "bank_account".Equals(stripeCharge.Source.Object) && stripeCharge.HasInvoice())
                {
                    // We're making an assumption that if an ACH payment is included in a transfer,
                    // and if we don't have the corresponding Donation in our system yet, that
                    // this is a mistake.  For instance, events delivered out of order from Stripe, and
                    // we received the transfer.paid before the invoice.payment_succeeded.
                    // In this scenario, we will go ahead and create the donation.
                    if (_donationService.CreateDonationForInvoice(stripeCharge.Invoice) != null)
                    {
                        _logger.Debug($"Creating donation for recurring gift payment {charge.Id}");
                        donation = _donationService.GetDonationByProcessorPaymentId(paymentId);
                    }
                    else
                    {
                        _logger.Error($"Donation not found for charge {charge.Id} on transfer {transfer.Id}, and failed to create a donation", e);
                        // ReSharper disable once PossibleIntendedRethrow
                        throw e;
                    }
                }
                else
                {
                    _logger.Error($"Donation not found for charge {charge.Id} on transfer {transfer.Id}, charge does not appear to be related to an ACH recurring gift");
                    // ReSharper disable once PossibleIntendedRethrow
                    throw e;
                }
            }
            return(donation);
        }
Beispiel #3
0
        public StripeTransfer CreateTransfer(Reimbursement reimbursement)
        {
            StripeConfiguration.SetApiKey(_stripe_key);
            var options = new StripeTransferCreateOptions
            {
                // TODO
            };

            var            service  = new StripeTransferService();
            StripeTransfer transfer = service.Create(options);

            return(transfer);
        }
        internal static Transfer TransformStripeTransferToTransfer(StripeTransfer stripeTransfer)
        {
            var transfer = new Transfer
            {
                TransferID = stripeTransfer.Id,
                Amount     = Sahara.Core.Common.Methods.Billing.ConvertStripeAmountToDecimals(stripeTransfer.Amount.ToString()),
                Status     = stripeTransfer.Status.ToString(),
                Type       = stripeTransfer.Type.ToString(),
                Date       = stripeTransfer.Date,
                Created    = stripeTransfer.Created,
            };

            if (stripeTransfer.Description != null)
            {
                transfer.Description = stripeTransfer.Description;
            }
            if (stripeTransfer.Destination != null)
            {
                transfer.Destination = stripeTransfer.Destination;
            }
            if (stripeTransfer.DestinationPayment != null)
            {
                transfer.DestinationPayment = stripeTransfer.DestinationPayment;
            }
            if (stripeTransfer.BalanceTransactionId != null)
            {
                transfer.BalanceTransactionID = stripeTransfer.BalanceTransactionId;
            }
            //if(stripeTransfer.BalanceTransaction != null)
            //{
            //transfer.BalanceTransaction = TransformStripeBalanceTransactionToBalanceTransaction(stripeTransfer.BalanceTransaction);
            //}
            if (stripeTransfer.FailureCode != null)
            {
                transfer.FailureCode = stripeTransfer.FailureCode;
            }
            if (stripeTransfer.FailureMessage != null)
            {
                transfer.FailureMessage = stripeTransfer.FailureMessage;
            }

            return(transfer);
        }
Beispiel #5
0
        public async Task <DisperseFundsResult> TransferCreated(Transfer transfer)
        {
            _logger.LogInformation(GetLogMessage("Transfer ID: {0}"), transfer.Id);
            var retVal = new DisperseFundsResult()
            {
                TransferId = transfer.Id,
                Amount     = 0,
            };

            if (transfer.Metadata.ContainsKey("invoice-id"))
            {
                string invoiceId = transfer.Metadata["invoice-id"];
                _logger.LogDebug(GetLogMessage("Invoice Id: {0}"), invoiceId);

                retVal.InvoiceId = invoiceId;

                var stripeTransfer = new StripeTransfer
                {
                    ObjectState     = ObjectState.Added,
                    Id              = transfer.Id,
                    Amount          = transfer.Amount / 100m,
                    Created         = transfer.Created,
                    DestinationId   = transfer.DestinationId,
                    AmountReversed  = transfer.AmountReversed / 100m,
                    Description     = transfer.Description,
                    InvoiceTransfer = new InvoiceTransfer()
                    {
                        ObjectState = ObjectState.Added,
                        InvoiceId   = invoiceId,
                        TransferId  = transfer.Id,
                    }
                };
                stripeTransfer.InjectFrom(transfer);

                var firstUpdate = _transfers.InsertOrUpdateGraph(stripeTransfer, true);
                _logger.LogDebug(GetLogMessage("{0} Transfer Records Created"), firstUpdate);

                if (firstUpdate > 0)
                {
                    stripeTransfer.ObjectState = ObjectState.Modified;
                    if (transfer.Metadata.ContainsKey("individual-payout-ids"))
                    {
                        if (Guid.TryParse(transfer.Metadata["person-id"], out var personId))
                        {
                            _logger.LogInformation(GetLogMessage("This is an individual payout transaction request"));

                            var payoutIds = transfer.Metadata["individual-payout-ids"].Split(",")
                                            .Select(Guid.Parse).ToArray();

                            _logger.LogDebug(GetLogMessage("Person Payout Ids: {0}"), payoutIds);

                            var payouts = await _individualPayoutIntents.Queryable()
                                          .Where(x => payoutIds.Contains(x.Id) && x.InvoiceTransferId == null)
                                          .ToListAsync();

                            _logger.LogDebug(GetLogMessage("{0} Individual Payouts to process"), payouts.Count);

                            foreach (var x in payouts)
                            {
                                _logger.LogInformation(GetLogMessage("Intent Id: {0}; Transfer Id: {1}"), x.Id, transfer.Id);

                                x.InvoiceTransferId = transfer.Id;
                                x.ObjectState       = ObjectState.Modified;

                                _individualPayoutIntents.InsertOrUpdateGraph(x);
                            }

                            var updatedPayouts = _individualPayoutIntents.Commit();
                            _logger.LogDebug(GetLogMessage("{0} Individual Payouts records updated"), updatedPayouts);

                            if (updatedPayouts > 0)
                            {
                                retVal.Succeeded = true;
                                retVal.Amount    = stripeTransfer.Amount;

                                await Task.Run(() =>
                                {
                                    RaiseEvent(new FundsDispersedToPersonEvent()
                                    {
                                        InvoiceId = invoiceId,
                                        PersonId  = personId,
                                        Amount    = stripeTransfer.Amount
                                    });
                                });
                            }
                        }
                    }

                    var organizationPayouts = 0;
                    if (transfer.Metadata.ContainsKey("organization-payout-ids"))
                    {
                        if (Guid.TryParse(transfer.Metadata["organization-id"], out var organizationId))
                        {
                            _logger.LogInformation(GetLogMessage("Processing Organization Payouts"));

                            var payoutIds = transfer.Metadata["organization-payout-ids"].Split(",").Select(Guid.Parse).ToArray();
                            var payouts   = await _organizationPayoutIntents.Queryable()
                                            .Where(x => payoutIds.Contains(x.Id) && x.OrganizationId == organizationId && x.InvoiceTransferId == null)
                                            .ToListAsync();

                            _logger.LogDebug(GetLogMessage("PayoutIds:{0}"), payoutIds);

                            foreach (var x in payouts)
                            {
                                _logger.LogInformation(GetLogMessage("Intent Id: {0}; Transfer Id: {1}"), x.Id, x.InvoiceTransferId);

                                x.InvoiceTransferId = transfer.Id;
                                x.ObjectState       = ObjectState.Modified;

                                _organizationPayoutIntents.InsertOrUpdateGraph(x);
                            }


                            var records = _organizationPayoutIntents.Commit();

                            _logger.LogDebug(GetLogMessage("{0} Organization Payout records updated"), records);

                            if (records > 0)
                            {
                                organizationPayouts++;
                            }

                            _logger.LogDebug(GetLogMessage("{0} Organization payouts updated"), organizationPayouts);

                            if (organizationPayouts > 0)
                            {
                                retVal.Succeeded = true;
                                retVal.Amount    = stripeTransfer.Amount;

                                await Task.Run(() =>
                                {
                                    RaiseEvent(new FundsDispersedToOrganizationEvent()
                                    {
                                        InvoiceId      = invoiceId,
                                        OrganizationId = organizationId,
                                        Amount         = stripeTransfer.Amount
                                    });
                                });
                            }
                        }
                        ;
                    }
                }
                else
                {
                    _logger.LogDebug(GetLogMessage("First update failed"));
                }
            }
            else
            {
                _logger.LogDebug(GetLogMessage("Not an invoice transfer"));
            }


            return(retVal);
        }
        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);
        }
Beispiel #8
0
        public void TestTransferPaid()
        {
            var transfer = new StripeTransfer
            {
                Id     = "tx9876",
                Amount = 1443
            };

            var e = new StripeEvent
            {
                LiveMode = true,
                Type     = "transfer.paid",
                Created  = DateTime.Now.AddDays(-1),
                Data     = new StripeEventData
                {
                    Object = JObject.FromObject(transfer)
                }
            };

            var paymentmetadata = new Dictionary <string, object> {
                { "crossroads_transaction_type", "payment" }
            };
            var donationmetadata = new Dictionary <string, object> {
                { "crossroads_transaction_type", "donation" }
            };

            var dateCreated = DateTime.Now;
            var charges     = new List <StripeCharge>
            {
                new StripeCharge
                {
                    Id       = "ch111",
                    Amount   = 111,
                    Fee      = 1,
                    Type     = "charge",
                    Created  = dateCreated.AddDays(1),
                    Metadata = paymentmetadata
                },
                new StripeCharge
                {
                    Id       = "ch222",
                    Amount   = 222,
                    Fee      = 2,
                    Type     = "charge",
                    Created  = dateCreated.AddDays(2),
                    Metadata = paymentmetadata
                },
                new StripeCharge
                {
                    Id       = "ch333",
                    Amount   = 333,
                    Fee      = 3,
                    Type     = "charge",
                    Created  = dateCreated.AddDays(3),
                    Metadata = donationmetadata
                },
                new StripeCharge
                {
                    Id       = "ch777",
                    Amount   = 777,
                    Fee      = 7,
                    Type     = "charge",
                    Created  = dateCreated.AddDays(4),
                    Metadata = donationmetadata
                },
                new StripeCharge
                {
                    Id       = "ch888",
                    Amount   = 888,
                    Fee      = 8,
                    Type     = "charge",
                    Created  = dateCreated.AddDays(5),
                    Metadata = donationmetadata
                },
                new StripeCharge
                {
                    Id       = "re999",
                    Amount   = 999,
                    Fee      = 9,
                    Type     = "payment_refund",
                    Created  = dateCreated.AddDays(8),
                    Metadata = donationmetadata
                },
                new StripeCharge
                {
                    Id       = "ch444",
                    Amount   = 444,
                    Fee      = 4,
                    Type     = "charge",
                    Created  = dateCreated.AddDays(6),
                    Metadata = donationmetadata
                },
                new StripeCharge
                {
                    Id       = "ch555",
                    Amount   = 555,
                    Fee      = 5,
                    Type     = "refund",
                    Created  = dateCreated.AddDays(7),
                    Metadata = donationmetadata
                }
            };


            _paymentService.Setup(mocked => mocked.GetPaymentByTransactionCode("ch111")).Returns(new PaymentDTO
            {
                PaymentId = 1111,
                BatchId   = null,
                Status    = DonationStatus.Pending
            });

            _paymentService.Setup(mocked => mocked.GetPaymentByTransactionCode("ch222")).Returns(new PaymentDTO
            {
                PaymentId = 2222,
                BatchId   = null,
                Status    = DonationStatus.Pending
            });

            _donationService.Setup(mocked => mocked.GetDonationByProcessorPaymentId("ch333")).Returns(new DonationDTO
            {
                Id      = "3333",
                BatchId = null,
                Status  = DonationStatus.Pending
            });

            _donationService.Setup(mocked => mocked.GetDonationByProcessorPaymentId("ch444")).Throws(new Exception("Not gonna do it, wouldn't be prudent."));

            _paymentProcessorService.Setup(mocked => mocked.GetChargeRefund("ch555")).Returns(new StripeRefund
            {
                Data = new List <StripeRefundData>
                {
                    new StripeRefundData()
                    {
                        Id     = "ch555",
                        Amount = "987",
                        Charge = new StripeCharge {
                            Id = "re_123456"
                        }
                    }
                }
            });

            _donationService.Setup(mocked => mocked.GetDonationByProcessorPaymentId("ch555")).Returns(new DonationDTO
            {
                Id      = "5555",
                BatchId = 1984
            });
            _donationService.Setup(mocked => mocked.GetDonationBatch(1984)).Returns(new DonationBatchDTO
            {
                Id = 5150,
                ProcessorTransferId = "OU812"
            });

            var stripeRefundData = new StripeRefundData
            {
                Id       = "re999",
                Amount   = "999",
                ChargeId = "ch999",
                Charge   = new StripeCharge
                {
                    Id = "ch999"
                },
                BalanceTransaction = new StripeBalanceTransaction
                {
                    Created = DateTime.Now
                }
            };
            var refund = new StripeRefund
            {
                Data = new List <StripeRefundData>
                {
                    stripeRefundData
                }
            };

            _paymentProcessorService.Setup(mocked => mocked.GetRefund("re999")).Returns(stripeRefundData);

            _donationService.Setup(
                mocked => mocked.CreateDonationForBankAccountErrorRefund(It.Is <StripeRefund>(r => r.Data != null && r.Data.Count == 1 && r.Data[0].Equals(stripeRefundData))))
            .Returns(876);

            var firstRefund = true;

            _donationService.Setup(mocked => mocked.GetDonationByProcessorPaymentId("re999")).Returns(() =>
            {
                if (firstRefund)
                {
                    firstRefund = false;
                    throw (new DonationNotFoundException("re999"));
                }

                return(new DonationDTO()
                {
                    Id = "9999",
                    BatchId = null,
                    Amount = 999
                });
            });

            _donationService.Setup(mocked => mocked.GetDonationByProcessorPaymentId("ch777")).Returns(new DonationDTO
            {
                Id      = "7777",
                BatchId = 2112
            });

            _donationService.Setup(mocked => mocked.GetDonationBatch(2112)).Returns(new DonationBatchDTO
            {
                Id = 2112,
                ProcessorTransferId = null
            });

            var first = true;

            _donationService.Setup(mocked => mocked.GetDonationByProcessorPaymentId("ch888")).Returns(() =>
            {
                if (first)
                {
                    first = false;
                    throw (new DonationNotFoundException("ch888"));
                }

                return(new DonationDTO()
                {
                    Id = "8888",
                    BatchId = null,
                    Amount = 888,
                    Status = DonationStatus.Declined
                });
            });

            _donationService.Setup(mocked => mocked.UpdateDonationStatus(refund.Data[0].ChargeId, 777, refund.Data[0].BalanceTransaction.Created, null)).Returns(9999);

            var invoice = new StripeInvoice
            {
                Amount = 100,
                Id     = "in_888"
            };

            _paymentProcessorService.Setup(mocked => mocked.GetCharge("ch888")).Returns(new StripeCharge
            {
                Source = new StripeSource
                {
                    Object = "bank_account"
                },
                Invoice = invoice
            });

            _donationService.Setup(mocked => mocked.CreateDonationForInvoice(invoice)).Returns(88);

            _paymentProcessorService.Setup(mocked => mocked.GetChargesForTransfer("tx9876")).Returns(charges);
            _donationService.Setup(mocked => mocked.GetDepositByProcessorTransferId("tx9876")).Returns((DepositDTO)null);

            _donationService.Setup(
                mocked => mocked.CreatePaymentProcessorEventError(e, It.IsAny <StripeEventResponseDTO>()));
            _paymentService.Setup(mocked => mocked.UpdatePaymentStatus(1111, 999, e.Created, null)).Returns(1111);
            _paymentService.Setup(mocked => mocked.UpdatePaymentStatus(2222, 999, e.Created, null)).Returns(2222);
            _donationService.Setup(mocked => mocked.UpdateDonationStatus(3333, 999, e.Created, null)).Returns(3333);
            _donationService.Setup(mocked => mocked.UpdateDonationStatus(7777, 999, e.Created, null)).Returns(7777);
            _donationService.Setup(mocked => mocked.UpdateDonationStatus(9999, 999, e.Created, null)).Returns(9999);
            _donationService.Setup(mocked => mocked.CreateDeposit(It.IsAny <DepositDTO>())).Returns(
                (DepositDTO o) =>
            {
                o.Id = 98765;
                return(o);
            });
            _donationService.Setup(mocked => mocked.CreateDonationBatch(It.IsAny <DonationBatchDTO>())).Returns((DonationBatchDTO o) => o);
            _paymentService.Setup(mocked => mocked.CreatePaymentBatch(It.IsAny <DonationBatchDTO>())).Returns((DonationBatchDTO o) => o);

            var result = _fixture.ProcessStripeEvent(e);

            Assert.IsNotNull(result);
            Assert.IsInstanceOf <TransferPaidResponseDTO>(result);
            var tp = (TransferPaidResponseDTO)result;

            Assert.AreEqual(8, tp.TotalTransactionCount);
            Assert.AreEqual(6, tp.SuccessfulUpdates.Count);
            Assert.AreEqual(charges.Take(6).Select(charge => charge.Id), tp.SuccessfulUpdates);
            Assert.AreEqual(2, tp.FailedUpdates.Count);
            Assert.AreEqual("ch555", tp.FailedUpdates[1].Key);
            Assert.AreEqual("ch444", tp.FailedUpdates[0].Key);
            Assert.AreEqual("Not gonna do it, wouldn't be prudent.", tp.FailedUpdates[0].Value);
            Assert.IsNotNull(tp.Batch);
            Assert.IsNotNull(tp.Deposit);
            Assert.IsNotNull(tp.Exception);

            _donationService.Verify(mocked => mocked.CreateDonationBatch(It.Is <DonationBatchDTO>(o =>
                                                                                                  o.BatchName.Matches(@"MP\d{12}D") &&
                                                                                                  o.SetupDateTime == o.FinalizedDateTime &&
                                                                                                  o.BatchEntryType == 555 &&
                                                                                                  o.ItemCount == 4 &&
                                                                                                  o.BatchTotalAmount == ((333 + 777 + 888 + 999) / Constants.StripeDecimalConversionValue) &&
                                                                                                  o.Donations != null &&
                                                                                                  o.Donations.Count == 4 &&
                                                                                                  o.DepositId == 98765 &&
                                                                                                  o.ProcessorTransferId.Equals("tx9876")
                                                                                                  )));

            _donationService.Verify(mocked => mocked.CreateDeposit(It.Is <DepositDTO>(o =>
                                                                                      o.DepositName.Matches(@"MP\d{12}") &&
                                                                                      !o.Exported &&
                                                                                      o.AccountNumber.Equals(" ") &&
                                                                                      o.BatchCount == 2 &&
                                                                                      o.DepositDateTime != null &&
                                                                                      o.DepositTotalAmount == ((transfer.Amount + 30) / Constants.StripeDecimalConversionValue) &&
                                                                                      o.ProcessorFeeTotal == (30 / Constants.StripeDecimalConversionValue) &&
                                                                                      o.DepositAmount == (transfer.Amount / Constants.StripeDecimalConversionValue) &&
                                                                                      o.Notes == null &&
                                                                                      o.ProcessorTransferId.Equals("tx9876")
                                                                                      )));

            _paymentProcessorService.VerifyAll();
            _donationService.VerifyAll();
        }