Machine Coding Problem

Digital Wallet

maco30maco60macoAllfintechcommand-patterndeadlock-preventiontransaction-rollbackacid
Commonly Asked By:PayPalBlockStripeCoinbase

Functional Scope (In-Scope)

  • Atomic Money Transfers: Perform money transactions securely ensuring complete atomic credit and debit steps.
  • Zero Floating-Point Errors: Use arbitrary-precision decimals (BigDecimal) to avoid float calculation flaws.
  • Strict Idempotency: Check idempotency keys before transaction processing to block duplicates.
  • Command Pattern for Rollbacks: Encapsulate operations inside Command objects, supporting robust undo/rollback mechanisms.
  • Append-Only Event Ledger: Maintain immutable transactional histories.

Explicit Boundaries (Out-of-Scope)

  • No Real Bank Card Integration: Bypasses live Visa/Mastercard processing flows or clearinghouses.
  • No Multi-Currency FX Engine: Overwrite parameters do not calculate real-time FX exchange conversion spreads.

Thread-safe, command-pattern-driven financial transfer blueprints in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

enum TransactionStatus { PENDING, SUCCESS, FAILED, ROLLED_BACK }

interface TransactionCommand {
    boolean execute();
    void undo();
    String getId();
    TransactionStatus getStatus();
    BigDecimal getAmount();
}

class Wallet {
    private final String id;
    private BigDecimal balance;
    private final ReentrantLock lock = new ReentrantLock();

    public Wallet(String id, BigDecimal initialBalance) {
        this.id = id;
        this.balance = initialBalance;
    }

    public String getId() { return id; }
    
    public BigDecimal getBalance() {
        lock.lock();
        try {
            return balance;
        } finally {
            lock.unlock();
        }
    }

    public ReentrantLock getLock() { return lock; }

    public boolean debit(BigDecimal amount) {
        if (balance.compareTo(amount) >= 0) {
            balance = balance.subtract(amount);
            return true;
        }
        return false;
    }

    public void credit(BigDecimal amount) {
        balance = balance.add(amount);
    }
}

class P2PTransferCommand implements TransactionCommand {
    private final String id;
    private final Wallet source;
    private final Wallet destination;
    private final BigDecimal amount;
    private TransactionStatus status;

    public P2PTransferCommand(String id, Wallet source, Wallet destination, BigDecimal amount) {
        this.id = id;
        this.source = source;
        this.destination = destination;
        this.amount = amount;
        this.status = TransactionStatus.PENDING;
    }

    @Override
    public boolean execute() {
        // Sort locks alphabetically by wallet ID to ensure deadlock immunity
        Wallet first = source.getId().compareTo(destination.getId()) < 0 ? source : destination;
        Wallet second = source.getId().compareTo(destination.getId()) < 0 ? destination : source;

        first.getLock().lock();
        try {
            second.getLock().lock();
            try {
                if (source.debit(amount)) {
                    destination.credit(amount);
                    status = TransactionStatus.SUCCESS;
                    return true;
                } else {
                    status = TransactionStatus.FAILED;
                    return false;
                }
            } finally {
                second.getLock().unlock();
            }
        } finally {
            first.getLock().unlock();
        }
    }

    @Override
    public void undo() {
        if (status != TransactionStatus.SUCCESS) return;

        Wallet first = source.getId().compareTo(destination.getId()) < 0 ? source : destination;
        Wallet second = source.getId().compareTo(destination.getId()) < 0 ? destination : source;

        first.getLock().lock();
        try {
            second.getLock().lock();
            try {
                if (destination.debit(amount)) {
                    source.credit(amount);
                    status = TransactionStatus.ROLLED_BACK;
                }
            } finally {
                second.getLock().unlock();
            }
        } finally {
            first.getLock().unlock();
        }
    }

    public String getId() { return id; }
    public TransactionStatus getStatus() { return status; }
    public BigDecimal getAmount() { return amount; }
}

class WalletService {
    private final Map<String, Wallet> wallets = new ConcurrentHashMap<>();
    private final Map<String, TransactionCommand> idempotencyStore = new ConcurrentHashMap<>();
    private final List<TransactionCommand> ledger = new CopyOnWriteArrayList<>();

    public void registerWallet(Wallet wallet) {
        wallets.put(wallet.getId(), wallet);
    }

    public Wallet getWallet(String id) {
        return wallets.get(id);
    }

    public TransactionCommand executeTransfer(String idempotencyKey, String srcId, String destId, BigDecimal amount) {
        if (idempotencyStore.containsKey(idempotencyKey)) {
            System.out.println("[Service] Returning cached transaction for idempotency key: " + idempotencyKey);
            return idempotencyStore.get(idempotencyKey);
        }

        Wallet src = wallets.get(srcId);
        Wallet dest = wallets.get(destId);
        if (src == null || dest == null) {
            throw new IllegalArgumentException("Invalid wallets involved in transfer");
        }

        String txnId = "TXN-" + UUID.randomUUID().toString().substring(0, 8);
        TransactionCommand command = new P2PTransferCommand(txnId, src, dest, amount);

        command.execute();
        ledger.add(command);
        idempotencyStore.put(idempotencyKey, command);

        return command;
    }

    public void rollbackTransaction(String txnId) {
        for (TransactionCommand cmd : ledger) {
            if (cmd.getId().equals(txnId)) {
                cmd.undo();
                System.out.println("[Service] Successfully rolled back Transaction: " + txnId);
                return;
            }
        }
        System.out.println("[Service] Transaction not found for rollback: " + txnId);
    }

    public List<TransactionCommand> getLedger() {
        return Collections.unmodifiableList(ledger);
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== Digital Wallet Concurrent Simulation ===");
        WalletService service = new WalletService();

        Wallet w1 = new Wallet("ACC-01", new BigDecimal("1000.00"));
        Wallet w2 = new Wallet("ACC-02", new BigDecimal("500.00"));
        service.registerWallet(w1);
        service.registerWallet(w2);

        ExecutorService executor = Executors.newFixedThreadPool(2);

        // Concurrent overlapping payments between ACC-01 and ACC-02 (locks are ordered, preventing deadlocks)
        executor.submit(() -> {
            service.executeTransfer("KEY-101", "ACC-01", "ACC-02", new BigDecimal("200.00"));
        });

        executor.submit(() -> {
            service.executeTransfer("KEY-102", "ACC-02", "ACC-01", new BigDecimal("100.00"));
        });

        executor.shutdown();
        executor.awaitTermination(2, TimeUnit.SECONDS);

        System.out.println("ACC-01 Balance: " + w1.getBalance());
        System.out.println("ACC-02 Balance: " + w2.getBalance());

        // Perform a rollback simulation
        TransactionCommand lastTxn = service.getLedger().get(0);
        System.out.println("\nRolling back transaction: " + lastTxn.getId() + " of amount: " + lastTxn.getAmount());
        service.rollbackTransaction(lastTxn.getId());

        System.out.println("ACC-01 Balance after rollback: " + w1.getBalance());
        System.out.println("ACC-02 Balance after rollback: " + w2.getBalance());
    }
}