public final class Transfer {
public static final class Request {
public final Account.ID source;
public final Account.ID destination;
public final Money amount;
public Request(Account.ID source, Account.ID destination, Money amount) {
this.source = source;
this.destination = destination;
this.amount = amount;
}
}
public static final class InsufficientFunds extends RuntimeException {}
private final Accounts accounts;
private final Compliance compliance;
private final Transactions transactions;
private final Notifier notifier;
public Transfer(Accounts accounts, Compliance compliance, Transactions transactions, Notifier notifier) {
this.accounts = accounts;
this.compliance = compliance;
this.transactions = transactions;
this.notifier = notifier;
}
public void submit(Request r) {
final var source = accounts.load(r.source);
final var destination = accounts.load(r.destination);
validate(source, destination, r.amount);
final var transaction = Transaction.fresh(r.source, r.destination, r.amount);
transactions.store(transaction);
notifyUsers(source, destination, transaction.id);
}
private void validate(Account source, Account destination, Money amount) {
checkCompliance(source, destination, amount);
checkBalance(source, amount);
}
private void checkCompliance(Account source, Account destination, Money amount) {
if (source.hasSameOwnerAs(destination)) {
return;
}
compliance.validateTransfer(source.owner, destination.owner, amount);
}
private void checkBalance(Account source, Money amount) {
if (source.canWithdraw(amount)) {
return;
}
throw new InsufficientFunds();
}
private void notifyUsers(Account source, Account destination, Transaction.ID transaction) {
notifier.notify(source.owner, transaction, "withdraw");
notifier.notify(destination.owner, transaction, "top up");
}
}