A few years ago, I worked on a problem in Apache Fineract (an open-source platform for building financial services and core banking systems) that sounds simple until you start looking closely at it: when a borrower makes a payment, exactly where should that money go?To solve this problem, I designed the Advanced Payment Allocation Strategy introduced in Apache Fineract 1.9.0 and implemented core parts of its behavior. The goal was to move away from repayment strategies where allocation rules were largely hard-coded, and make those rules much more configurable.Advanced Payment Allocation allows financial institutions to define how different transaction types should be applied across past-due, currently due, and future installments; how principal, interest, fees, and penalties should be prioritized; and how payments made in advance should affect future installments. It also introduced chronological reprocessing of transactions and charges and supports different horizontal and vertical approaches to allocation. I later presented the work at Community Over Code Europe 2024 conference, where I discussed both the business requirements behind the strategy and the mechanics used to implement it.However, building it exposed a much broader financial software design problem, which I found interesting: how do you translate a financial institution’s business policy into precise, deterministic software behavior that remains correct as transactions, schedules, and product rules evolve? Payment allocation is a good example of what happens when a financial institution's business policy has to become precise, deterministic software behavior. A rule that sounds straightforward to a product or finance team can quickly become a combination of priorities, state transitions, chronology, lifecycle events, and exceptions. That is where payment allocation connects to a broader idea, Financial Product Engineering: the discipline of translating financial intent into precise, reliable, and evolvable software behavior.To see why, let’s start with a (deceptively) simple question: a borrower makes a payment of €100. Where does the money go? At first, this sounds like one of the simpler questions a lending system should be able to answer. Reduce the balance by €100… but which balance? Does the payment cover overdue principal first? Accrued interest? A fee? A penalty? The current installment? A future installment? If some of the money remains after the current obligation is satisfied, does it reduce the next installment? The final installment? Should it be spread across future installments? And should a repayment follow the same rules as a merchant refund, goodwill credit, charge adjustment, or another type of transaction? Once you start answering those questions, payment allocation stops looking like a payment-processing detail - it becomes financial product behavior.While designing and implementing core parts of Apache Fineract's Advanced Payment Allocation strategy, this was one of the engineering lessons that became clear to me. The difficult problem was not taking an amount and distributing it across balances; it was translating financial policy into software behavior precisely enough that the system could apply it consistently, explainably, and safely as the loan evolved. And that problem goes beyond payment allocation - I think it illustrates a broader discipline that financial software teams increasingly need to treat explicitly: Financial Product Engineering. Payment allocation is a good place to see why that matters.The €100 Payment Is Really a Policy DecisionLet’s say a borrower has the following obligations:Past-due principal€50Past-due interest€15Past-due fee€0Past-due penalty€0Due principal€40Due interest€10Due fee€5Due penalty€0So the borrower sends €100. There is no universal answer to how those €100 should be applied. One lender might prioritize overdue interest before principal, another might prioritize principal, and another may require fees to be settled first. The priority might also depend on the transaction type. And if the payment exceeds everything currently due, there is another decision to make: what does the remaining amount do to the future schedule? Each answer represents a financial policy, and software has to turn that policy into something deterministic. For example:Past-due interestPast-due principalPast-due feePast-due penaltyDue interestDue principalDue feePast-due penaltyIn advance principalIn advance feeIn advance penaltyIn advance interestAnd another product could have a completely different sequence. The important observation is that this ordering is not metadata about the loan. Once the system starts processing transactions, the ordering becomes the product's behavior: it determines balances, it influences the repayment schedule, it affects what becomes overdue, it can influence interest calculations and accounting consequences, and it changes the financial state from which the next event will be processed. A configuration field in a lending platform can therefore carry far more meaning than a typical software setting - it can encode financial policy.Hard-Coding Financial Policy Works Until the Product Needs to ChangeApache Fineract has supported different transaction-processing strategies for many years. The problem with traditional repayment strategies was not that they couldn't allocate payments (they could). The problem was that the allocation behavior was embedded in predefined strategies. If a particular institution needed a different sequence, the usual engineering response was to represent another set of business rules in code. That approach works surprisingly well when the product is stable; it becomes painful when financial products start evolving.Fineract's Advanced Payment Allocation model was introduced specifically to allow allocation rules to be configured dynamically by transaction type rather than having those rules fixed inside separate repayment strategies. The model can distinguish past-due, due and future installments; prioritize principal, interest, fees and penalties; define how future installments should be treated; and chronologically reprocess transactions and charges. That change might sound like a normal move from hard-coded logic to configuration, but in financial software, there is a deeper architectural shift underneath it.The system is moving from: Financial policy → Developer interpretation → CodeTo: Financial policy → Explicit product rules → Generic execution model → Financial stateThe goal is to make the financial rule itself a first-class concept in the system.Configurable Does Not Mean VagueThere is a trap here. When engineers hear "make it configurable," the instinct can be to expose enough switches and parameters that almost anything becomes possible. In financial systems, that could be dangerous. Configurability does not remove the need for precision - it increases it. If a system allows payment allocation to vary, then it needs an exact vocabulary for expressing that variation. For example, there are at least two different dimensions involved.The first is which installment should receive money: Past due / Due now / FutureThe second is which financial component should receive money: Principal / Interest / Fee / PenaltyThen, future payments introduce another question. If money should be allocated to installments that are not yet due, which ones? Apache Fineract's model supports different approaches, including allocating toward the next installment, the last installment, or re-amortizing an amount across future installments. Those are not cosmetic choices, they produce different financial outcomes. This is why good financial configuration needs strong semantics. A useful financial rule should be flexible enough to represent product policy but constrained enough that its meaning remains clear.In other words: The purpose of financial configuration is not unlimited flexibility, it is controlled variation with deterministic financial meaning.There Are Multiple Ways to Ask "What Comes First?"Payment allocation also taught me that even something as simple as priority can have more than one interpretation. Let’s imagine three overdue installments: each contains principal, interest, and perhaps fees or penalties. Should the system completely satisfy the oldest installment before moving on to the next? Conceptually:Installment 1penaltyfeeinterestprincipalthen: Installment 2penaltyfeeinterestprincipalOr should it prioritize one balance category across the relevant installments? For example:all applicable penaltiesthen all applicable feesthen all applicable interestthen all applicable principalThese different ways of traversing the financial state can lead to different results. They also demonstrate why product specialists and software engineers need a shared model. Saying "penalties come first" is not necessarily sufficient, an engineer still needs to know: Penalties where? Across which installments? Relative to which other dimensions? Under which transaction type? At which point in the loan lifecycle? Many difficult financial-software bugs begin in the space between a sentence that sounds clear to a human and a rule that must be unambiguous to a machine.Chronology Is Part of the Financial ModelThere was another aspect of Advanced Payment Allocation that I found particularly important: chronological processing. Imagine this sequence:January 10 - repaymentJanuary 15 - charge createdJanuary 20 - another repaymentNow imagine that something causes the system to reprocess the loan. The January 10 payment should not suddenly behave as though the January 15 charge already existed, as that would rewrite financial history. The system needs to reproduce what the financial state should have been at each point in the sequence. This is why Advanced Payment Allocation processes transactions and charges chronologically. It is not merely a technical implementation preference; it protects the relationship between an event and the financial state that existed when that event occurred. This leads to a broader principle:In financial software, the result of an event often depends not only on what happened, but on what the system knew financially at the moment it happened.That becomes really important when dealing with reversals, corrections, backdated transactions, restructures, refunds, and other lifecycle events. A loan cannot always be understood by looking only at its current database state, sometimes you need to understand the sequence that created that state.A Loan Is a Financial State MachineThat is why I tend to think about lending systems as financial state machines.A payment is not simply: balance = balance - paymentIt is closer to: current financial state + transaction + applicable product rules → new financial stateAnd the new financial state becomes the input for whatever happens next. The consequences compound.Consider: Disbursement → Repayment → Partial repayment → Fee → Late payment → Charge adjustment → Prepayment → ReversalEvery step depends on the state produced by the previous steps. This is why the happy path is rarely the hardest part of a lending engine. Creating a loan, generating a schedule, and processing an ordinary repayment might demonstrate that the basic model works. The more revealing questions come later.What happens when the payment arrives early?What happens when it arrives late?What happens when it only covers part of the amount due?What happens when the transaction gets reversed?What happens when a previous charge changes?What happens when the loan is restructured?What happens when multiple financial behaviors interact?Those are not secondary scenarios, they are where the product model is actually tested.Financial Intent Has to Survive Several TranslationsI find it useful to describe this using what I call the Financial Product Engineering Chain: Financial Intent → Product Rules → Software Behavior → Financial State → Validation → EvolutionPayment allocation can travel through the entire chain.Financial Intent: A lender decides how borrower payments should satisfy obligations.Product Rules: That policy becomes explicit priorities covering installments, balance components, transaction types, and future allocation.Software Behavior: The platform executes those rules when transactions occur.Financial State: Principal, interest, fees, penalties, installments, and schedules change as a result.Validation: The team needs to prove that sequences of events produce the expected financial outcome.Evolution: The financial institution changes its product, requiring different rules without corrupting existing contracts.The difficult part is preserving meaning all the way through. A financial policy can be perfectly reasonable at the intent level and still become incorrect software behavior if one of those translations is ambiguous.Existing Loans Make Product Evolution HarderChanging a rule in financial software introduces another problem that conventional configuration systems do not always face. Existing contracts already have financial history. Let’s say a lender changes its payment-allocation policy today. Should an existing loan that originated six months ago suddenly start following the new behavior? Sometimes, maybe. Sometimes not. Apache Fineract's Advanced Payment Allocation requirements explicitly state that changes made to payment-allocation rules at loan-product level apply to newly created loan accounts, while existing accounts retain the old rules. That is an important architectural idea.Financial Products Evolve, but Financial Contracts PersistSoftware has to represent both realities. That means product configuration is not simply about the current settings, it is also about knowing which financial behavior applies to which contract. Once a platform has thousands or millions of live loans, changing product behavior safely becomes a versioning problem, a lifecycle problem, and a data-integrity problem at the same time. This is why I believe the ability to change a financial product is increasingly a technology capability. The question is not only if the system can support this lending product. A better question is: If the product changes next year, can we introduce the new behavior without losing the meaning of everything that already happened?Testing Numbers Is Not EnoughThis also changes how financial software should be tested. A test such as: payment allocation result = €100 doesn't tell us much. The interesting tests describe financial behavior. For example, given:two overdue installmentsa current installmentunpaid interestunpaid principalWhen: a €100 repayment is madeThen: allocation follows the configured priorityAnd: the correct installments are updatedAnd: the remaining balances are correctThen introduce another event. Reverse the payment. Add a charge between two historical transactions. Make the next payment early. Change the sequence. Use another transaction type. The unit of correctness increasingly becomes the financial lifecycle, not an isolated calculation. This is one reason financial-domain knowledge matters so much in lending engineering. A technically correct implementation of the wrong financial rule is still wrong.Product and Engineering Cannot Live in Separate WorldsPayment allocation might sound like an unusually specialized example. I think it reveals something much broader about building financial products. Traditional product development often assumes a sequence:Business defines requirements → Product refines them → Engineering implements themThat separation becomes problematic when the requirement itself contains complex executable behavior. An engineer designing payment allocation needs to understand why allocation order matters; a product specialist needs to understand what must be explicit for the software to execute the policy deterministically; a financial-domain specialist needs to understand the constraints of transaction histories, schedules, reversals, and state. None of those disciplines can safely operate only at the end of the chain, they have to converge much earlier. That convergence is what I mean by Financial Product Engineering - it is the point where all three have to meet.The Bigger Lesson From Payment AllocationAdvanced Payment Allocation was designed to solve a specific lending problem, but the architectural lesson I take from it is much broader.Financial products contain behavior.Interest accrual contains behavior.Fees contain behavior.Repayment schedules contain behavior.Delinquency contains behavior.Prepayment contains behavior.Restructuring contains behavior.Corrections and reversals contain behavior.If those rules are buried throughout application code, changing the financial product becomes increasingly risky. If they become configurable without precise semantics, the system becomes flexible but difficult to reason about. The engineering challenge lies between those two extremes. We need financial behavior that is explicit enough to understand, precise enough to execute, constrained enough to trust, testable enough to validate, and evolvable enough to change. That is the deeper reason I find payment allocation interesting. The question was never simply where a €100 payment goes, but how do you take a financial institution's intent about that €100 and turn it into software behavior that remains correct through every event that follows? That is a Financial Product Engineering problem.Disclosure: I am a member of the Apache Software Foundation and contributed to Apache Fineract, including work on the Advanced Payment Allocation functionality discussed in this article. This article is written in a personal capacity and is not sponsored.