Code · Interview problems · Problems
Payment Invoice Matching
Question
Topic: String parsing / Sorting · Difficulty: Easy → Medium
A payment is paymentId,amount,memo where the memo may say Paying off: <invoiceId> or Paying for: <invoiceId>.
Invoices are invoiceId,amount,dueDate.
Part 1: match by memo
Read the invoice id from the memo (case-insensitive marker) and return
"<paymentId> pays off <amount> for <invoiceId> due on <dueDate>",
or "<paymentId> could not be matched to any invoice".
Part 2: fall back to amount
If the memo doesn’t name a known invoice, match an invoice with the same amount. If several match, pick the earliest due date (then the smallest id).
Part 3: forgiveness
If nothing matches exactly, allow a short payment within forgiveness
(amount < invoiceAmount ≤ amount + forgiveness) and append ", forgiving <forgiveness>".
Approach: parse once, then try the matchers in order: memo, exact amount, forgiveness. Break ties by sorting on (dueDate, id).
PaymentInvoiceMatchPart1.java 45 lines GitHub ↗
/**
* Problem: Payment Invoice Matching
* Difficulty: Medium
* Topic: String parsing / Sorting
* Question: Match a payment to an invoice by memo, then by equal amount (earliest due date), then within a forgiveness amount. Full statement in README.md.
*/
package dsa.question7;
import java.util.HashMap;
public class PaymentInvoiceMatchPart1 {
public static class Inovice{
String invoiceID;
Long amount;
String dueDate;
public Inovice(String invoiceID, Long amount, String dueDate) {
this.invoiceID = invoiceID ; this.amount = amount; this.dueDate = dueDate;
}
}
public static String paymentMatch(String payment, String[] invoices) {
HashMap<String, Inovice> invoiceDetail = new HashMap<>();
for(int i=0; i< invoices.length; i++) {
String[] invoice = invoices[i].split(",");
String invoiceId = invoice[0] , dueDate = invoice[2];
Long amount = Long.parseLong(invoice[1]);
invoiceDetail.put(invoiceId, new Inovice(invoiceId, amount, dueDate));
}
String[] paymentDetail = payment.split("," , 3);
String paymentId = paymentDetail[0];
Long amount = Long.parseLong(paymentDetail[1]);
String matchInvoice = paymentDetail[2];
String lowered = matchInvoice.toLowerCase();
String invoiceID = "";
for(String marker : new String[] {"paying off:" , "paying for:"}) {
int index = lowered.indexOf(marker);
if(index != -1) {
invoiceID = matchInvoice.substring(index + marker.length()).trim();
break;
}
}
if(!invoiceDetail.containsKey(invoiceID)) {
return paymentId + " could not be matched to any invoice";
}
String duedate = invoiceDetail.get(invoiceID).dueDate;
return paymentId + " pays off " + amount + " for " + invoiceID + " due on " + duedate;
}
} PaymentInvoiceMatchPart2.java 72 lines GitHub ↗
package dsa.question7;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
public class PaymentInvoiceMatchPart2 {
public static class Inovice{
String invoiceID;
Long amount;
String dueDate;
public Inovice(String invoiceID, Long amount, String dueDate) {
this.invoiceID = invoiceID ; this.amount = amount; this.dueDate = dueDate;
}
}
public static class Payment {
String paymentId;
Long amount;
String invoiceId;
public Payment(String paymentId, Long amount , String invoiceId) {
this.paymentId = paymentId ; this.amount = amount ; this.invoiceId = invoiceId;
}
}
public static String paymentMatch(String payment, String[] invoices) {
List<Inovice> invoiceDetail = new ArrayList<>();
for(int i=0; i< invoices.length; i++) {
String[] invoice = invoices[i].split(",");
String invoiceId = invoice[0] , dueDate = invoice[2];
Long amount = Long.parseLong(invoice[1]);
invoiceDetail.add(new Inovice(invoiceId, amount, dueDate));
}
String[] paymentDetail = payment.split("," , 3);
String paymentId = paymentDetail[0];
Long amount = Long.parseLong(paymentDetail[1]);
String matchInvoice = paymentDetail[2];
String lowered = matchInvoice.toLowerCase();
String invoiceID = "";
for(String marker : new String[] {"paying off:" , "paying for:"}) {
int index = lowered.indexOf(marker);
if(index != -1) {
invoiceID = matchInvoice.substring(index + marker.length()).trim();
break;
}
}
Payment paymentParse = new Payment(paymentId, amount, invoiceID);
for(Inovice book : invoiceDetail) {
if(book.invoiceID.equals(invoiceID)) {
String duedate = book.dueDate;
return paymentId + " pays off " + amount + " for " + invoiceID + " due on " + duedate;
}
}
Inovice recociller = reconcileOnAmount(paymentParse, invoiceDetail);
if(recociller != null) {
return paymentId + " pays off " + amount + " for " + recociller.invoiceID + " due on " + recociller.dueDate;
}
return paymentId + " could not be matched to any invoice";
}
private static Inovice reconcileOnAmount(Payment payment, List<Inovice> invoiceDetail ) {
List<Inovice> amountMatched = new ArrayList<>();
for(Inovice book : invoiceDetail) {
if(book.amount.equals(payment.amount)) {
amountMatched.add(book);
}
}
if(amountMatched.isEmpty()) {
return null;
}
// sort on due date and return first
amountMatched.sort(Comparator.comparing( (Inovice a) -> a.dueDate )
.thenComparing(a -> a.invoiceID));
return amountMatched.get(0);
}
} PaymentInvoiceMatchPart3.java 94 lines GitHub ↗
package dsa.question7;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class PaymentInvoiceMatchPart3 {
public static class Inovice{
String invoiceID;
Long amount;
String dueDate;
public Inovice(String invoiceID, Long amount, String dueDate) {
this.invoiceID = invoiceID ; this.amount = amount; this.dueDate = dueDate;
}
}
public static class Payment {
String paymentId;
Long amount;
String invoiceId;
public Payment(String paymentId, Long amount , String invoiceId) {
this.paymentId = paymentId ; this.amount = amount ; this.invoiceId = invoiceId;
}
}
public static String paymentMatch(String payment, String[] invoices, Long forgiveness) {
List<Inovice> invoiceDetail = new ArrayList<>();
for(int i=0; i< invoices.length; i++) {
String[] invoice = invoices[i].split(",");
String invoiceId = invoice[0] , dueDate = invoice[2];
Long amount = Long.parseLong(invoice[1]);
invoiceDetail.add(new Inovice(invoiceId, amount, dueDate));
}
String[] paymentDetail = payment.split("," , 3);
String paymentId = paymentDetail[0];
Long amount = Long.parseLong(paymentDetail[1]);
String matchInvoice = paymentDetail[2];
String lowered = matchInvoice.toLowerCase();
String invoiceID = "";
for(String marker : new String[] {"paying off:" , "paying for:"}) {
int index = lowered.indexOf(marker);
if(index != -1) {
invoiceID = matchInvoice.substring(index + marker.length()).trim();
break;
}
}
Payment paymentParse = new Payment(paymentId, amount, invoiceID);
for(Inovice book : invoiceDetail) {
if(book.invoiceID.equals(invoiceID)) {
String duedate = book.dueDate;
return paymentId + " pays off " + amount + " for " + invoiceID + " due on " + duedate;
}
}
Inovice recocillerOnExactMatch = recocillerOnExactMatch(paymentParse, invoiceDetail);
if(recocillerOnExactMatch != null) {
return paymentId + " pays off " + amount + " for " + recocillerOnExactMatch.invoiceID + " due on " + recocillerOnExactMatch.dueDate;
}
Inovice reconcileOnForgiveness = reconcileOnForgiveness(paymentParse, invoiceDetail, forgiveness);
if(reconcileOnForgiveness != null) {
return paymentId + " pays off " + amount + " for " + reconcileOnForgiveness.invoiceID + " due on " + reconcileOnForgiveness.dueDate+ ", forgiving " + forgiveness;
}
return paymentId + " could not be matched to any invoice";
}
private static Inovice recocillerOnExactMatch(Payment payment, List<Inovice> invoiceDetail) {
List<Inovice> amountMatched = new ArrayList<>();
for(Inovice book : invoiceDetail) {
Long amountDue = book.amount;
Long amountPay = payment.amount;
if(amountPay.equals(amountDue)) {
amountMatched.add(book);
}
}
if(amountMatched.isEmpty()) {
return null;
}
// sort on due date and return first
amountMatched.sort(Comparator.comparing( (Inovice a) -> a.dueDate )
.thenComparing(a -> a.invoiceID));
return amountMatched.get(0);
}
private static Inovice reconcileOnForgiveness(Payment payment, List<Inovice> invoiceDetail, Long forgiveness) {
List<Inovice> amountMatched = new ArrayList<>();
for(Inovice book : invoiceDetail) {
Long amountDue = book.amount;
Long amountPay = payment.amount;
if(amountPay < amountDue && amountDue <= amountPay + forgiveness) {
amountMatched.add(book);
}
}
if(amountMatched.isEmpty()) {
return null;
}
// sort on due date and return first
amountMatched.sort(Comparator.comparing( (Inovice a) -> a.dueDate )
.thenComparing(a -> a.invoiceID));
return amountMatched.get(0);
}
}