Я работаю в крупной шведской страховой компании. Я отвечаю за службу, которая сопоставляет банковские платежи со счетами. Я не буду подробно останавливаться на этой логике сопоставления. Но одним из результатов матча является то, что сумма платежа отличается от суммы в счете. У нас есть IAmountDeviationStrategy
который помечен на каждом счете-фактуре. Здесь RefundStrategy
public class RefundStrategy : IAmountDeviationStrategy
{
public AmountDeviationStrategy Strategy => AmountDeviationStrategy.Refund;
public PaymentInvoiceMatchResult Execute(MatchJob job)
{
if (job.Diff < 0)
return new PaymentInvoiceMatchResultBuilder(job)
.PaymentCategory(PaymentCategory.WrongPayment)
.RefundAll(RefundReason.UnderPaid);
var invoiceAmount = Math.Abs(job.Invoice.Amount);
var payingPayments = GetPaymentsComboClosestToInvoiceAmount(job.Payments, invoiceAmount);
var refunds = job.Payments.Except(payingPayments).ToList();
var result = new PaymentInvoiceMatchResultBuilder(job, refunds)
.PaymentCategory(PaymentCategory.WrongPayment)
.Refund(refunds);
if (payingPayments.Sum(p => p.Amount) != invoiceAmount)
result.Refund<RefundSplitPaymentCommand>(payingPayments.First(p => p.Amount >= job.Diff), split => split.Amount = payingPayments.Sum(p => p.Amount) - invoiceAmount);
return result
.QueueCommand(new MapInvoicePaymentsCommand { InvoiceId = job.Invoice.ExternalId, Payments = payingPayments.Select(p => p.ExternalId) });
}
private IReadOnlyCollection<IPaymentMatchInfo> GetPaymentsComboClosestToInvoiceAmount(IReadOnlyCollection<IPaymentMatchInfo> payments, decimal invoiceAmount)
{
var combos = Enumerable.Range(1, payments.Count)
.SelectMany(i => new Combinations<IPaymentMatchInfo>(payments, new ReferenceCompare<IPaymentMatchInfo>(), i))
.ToList();
var payingPayments = combos
.Where(c => c.Sum(p => p.Amount) >= invoiceAmount)
.OrderBy(combo => Math.Abs(combo.Sum(p => p.Amount) - invoiceAmount))
.First();
return payingPayments;
}
Если платеж не соответствует счету, мы просто возвращаем платеж. Если его переплачивают, начинается самое интересное. Платежей может быть несколько. Чаще всего клиент заплатил один раз и переплатил. Вторым по распространенности является двойная оплата, то есть два платежа, которые в два раза превышают сумму счета. Третий и редкий случай — это множественные платежи, которые случайным образом переплачиваются (а не вдвое больше).
Во всяком случае, я грубо взламываю эту проблему. Я нахожу все комбинации платежей, используя небольшой класс Combination https://pastebin.com/BDvRmmgD (Не мной написано)
Когда у меня есть все комбинации, я беру одну, наиболее близкую к сумме счета. Я возвращаю деньги всем остальным. Затем я проверяю, соответствуют ли выплачиваемые платежи сумме (что происходит в сценарии с двойной оплатой). Если они не суммируют, я делю одну из выплат и возвращаю переплаченную часть.
Затем я повторно запускаю соответствующую команду MapInvoicePaymentsCommand
. На этот раз счет-фактура и платеж попадут в путь оплаченного результата.
Я думаю, что грубая сила довольно элегантна. Платежей никогда не будет больше, чем несколько, поэтому это не будет проблемой для производительности.