@@ -27,22 +27,54 @@ roundPeriodicPayment(
2727 return roundToAsset (asset, periodicPayment, scale, Number::upward);
2828}
2929
30- // / This structure is explained in the XLS-66 spec, section 3.2.4.4 (Failure
31- // / Conditions)
30+ /* Represents the breakdown of amounts to be paid and changes applied to the
31+ * Loan object while processing a loan payment.
32+ *
33+ * This structure is returned after processing a loan payment transaction and
34+ * captures the amounts that need to be paid. The actual ledger entry changes
35+ * are made in LoanPay based on this structure values.
36+ *
37+ * The sum of principalPaid, interestPaid, and feePaid represents the total
38+ * amount to be deducted from the borrower's account. The valueChange field
39+ * tracks whether the loan's total value increased or decreased beyond normal
40+ * amortization.
41+ *
42+ * This structure is explained in the XLS-66 spec, section 3.2.4.2 (Payment
43+ * Processing).
44+ */
3245struct LoanPaymentParts
3346{
34- // / principal_paid is the amount of principal that the payment covered.
47+ // The amount of principal paid that reduces the loan balance.
48+ // This amount is subtracted from sfPrincipalOutstanding in the Loan object
49+ // and paid to the Vault
3550 Number principalPaid = numZero;
36- // / interest_paid is the amount of interest that the payment covered.
51+
52+ // The total amount of interest paid to the Vault.
53+ // This includes:
54+ // - Tracked interest from the amortization schedule
55+ // - Untracked interest (e.g., late payment penalty interest)
56+ // This value is always non-negative.
3757 Number interestPaid = numZero;
38- /* *
39- * value_change is the amount by which the total value of the Loan changed.
40- * If value_change < 0, Loan value decreased.
41- * If value_change > 0, Loan value increased.
42- * This is 0 for regular payments.
43- */
58+
59+ // The change in the loan's total value outstanding.
60+ // - If valueChange < 0: Loan value decreased
61+ // - If valueChange > 0: Loan value increased
62+ // - If valueChange = 0: No value adjustment
63+ //
64+ // For regular on-time payments, this is always 0. Non-zero values occur
65+ // when:
66+ // - Overpayments reduce the loan balance beyond the scheduled amount
67+ // - Late payments add penalty interest to the loan value
68+ // - Early full payment may increase or decrease the loan value based on
69+ // terms
4470 Number valueChange = numZero;
45- // / feePaid is amount of fee that is paid to the broker
71+
72+ /* The total amount of fees paid to the Broker.
73+ * This includes:
74+ * - Tracked management fees from the amortization schedule
75+ * - Untracked fees (e.g., late payment fees, service fees, origination
76+ * fees) This value is always non-negative.
77+ */
4678 Number feePaid = numZero;
4779
4880 LoanPaymentParts&
@@ -52,46 +84,71 @@ struct LoanPaymentParts
5284 operator ==(LoanPaymentParts const & other) const ;
5385};
5486
55- /* * This structure describes the initial " computed" properties of a loan.
87+ /* Describes the initial computed properties of a loan.
5688 *
57- * It is used at loan creation and when the terms of a loan change, such as
58- * after an overpayment.
89+ * This structure contains the fundamental calculated values that define a
90+ * loan's payment structure and amortization schedule. These properties are
91+ * computed:
92+ * - At loan creation (LoanSet transaction)
93+ * - When loan terms change (e.g., after an overpayment that reduces the loan
94+ * balance)
5995 */
6096struct LoanProperties
6197{
98+ // The unrounded amount to be paid at each regular payment period.
99+ // Calculated using the standard amortization formula based on principal,
100+ // interest rate, and number of payments.
101+ // The actual amount paid in the LoanPay transaction must be rounded up to
102+ // the precision of the asset and loan.
62103 Number periodicPayment;
104+
105+ // The total amount the borrower will pay over the life of the loan.
106+ // Equal to periodicPayment * paymentsRemaining.
107+ // This includes principal, interest, and management fees.
63108 Number totalValueOutstanding;
109+
110+ // The total management fee that will be paid to the broker over the
111+ // loan's lifetime. This is a percentage of the total interest (gross)
112+ // as specified by the broker's management fee rate.
64113 Number managementFeeOwedToBroker;
114+
115+ // The scale (decimal places) used for rounding all loan amounts.
116+ // This is the maximum of:
117+ // - The asset's native scale
118+ // - A minimum scale required to represent the periodic payment accurately
119+ // All loan state values (principal, interest, fees) are rounded to this
120+ // scale.
65121 std::int32_t loanScale;
122+
123+ // The principal portion of the first payment.
66124 Number firstPaymentPrincipal;
67125};
68126
69- /* * This structure captures the current state of a loan and all the
70- relevant parts.
71-
72- Whether the values are raw (unrounded) or rounded will
73- depend on how it was computed.
74-
75- Many of the fields can be derived from each other, but they're all provided
76- here to reduce code duplication and possible mistakes.
77- e.g.
78- * interestOutstanding = valueOutstanding - principalOutstanding
79- * interestDue = interestOutstanding - managementFeeDue
127+ /* * This structure captures the parts of a loan state.
128+ *
129+ * Whether the values are raw (unrounded) or rounded will depend on how it was
130+ * computed.
131+ *
132+ * Many of the fields can be derived from each other, but they're all provided
133+ * here to reduce code duplication and possible mistakes.
134+ * e.g.
135+ * * interestOutstanding = valueOutstanding - principalOutstanding
136+ * * interestDue = interestOutstanding - managementFeeDue
80137 */
81138struct LoanState
82139{
83- // / Total value still due to be paid by the borrower.
140+ // Total value still due to be paid by the borrower.
84141 Number valueOutstanding;
85- // / Prinicipal still due to be paid by the borrower.
142+ // Principal still due to be paid by the borrower.
86143 Number principalOutstanding;
87- // / Interest still due to be paid TO the Vault.
144+ // Interest still due to be paid to the Vault.
88145 // This is a portion of interestOutstanding
89146 Number interestDue;
90- // / Management fee still due to be paid TO the broker.
147+ // Management fee still due to be paid to the broker.
91148 // This is a portion of interestOutstanding
92149 Number managementFeeDue;
93150
94- // / Interest still due to be paid by the borrower.
151+ // Interest still due to be paid by the borrower.
95152 Number
96153 interestOutstanding () const
97154 {
@@ -199,42 +256,133 @@ namespace detail {
199256
200257enum class PaymentSpecialCase { none, final , extra };
201258
202- // / This structure is used internally to compute the breakdown of a
203- // / single loan payment
259+ /* Represents a single loan payment component parts.
260+
261+ * This structure captures the "delta" (change) values that will be applied to
262+ * the tracked fields in the Loan ledger object when a payment is processed.
263+ *
264+ * These are called "deltas" because they represent the amount by which each
265+ * corresponding field in the Loan object will be reduced.
266+ * They are "tracked" as they change tracked loan values.
267+ */
204268struct PaymentComponents
205269{
206- // tracked values are rounded to the asset and loan scale, and correspond to
207- // fields in the Loan ledger object.
208- // trackedValueDelta modifies sfTotalValueOutstanding.
270+ // The change in total value outstanding for this payment.
271+ // This amount will be subtracted from sfTotalValueOutstanding in the Loan
272+ // object. Equal to the sum of trackedPrincipalDelta,
273+ // trackedInterestPart(), and trackedManagementFeeDelta.
209274 Number trackedValueDelta;
210- // trackedPrincipalDelta modifies sfPrincipalOutstanding.
275+
276+ // The change in principal outstanding for this payment.
277+ // This amount will be subtracted from sfPrincipalOutstanding in the Loan
278+ // object, representing the portion of the payment that reduces the
279+ // original loan amount.
211280 Number trackedPrincipalDelta;
212- // trackedManagementFeeDelta modifies sfManagementFeeOutstanding. It will
213- // not include any "extra" fees that go directly to the broker, such as late
214- // fees.
281+
282+ // The change in management fee outstanding for this payment.
283+ // This amount will be subtracted from sfManagementFeeOutstanding in the
284+ // Loan object. This represents only the tracked management fees from the
285+ // amortization schedule and does not include additional untracked fees
286+ // (such as late payment fees) that go directly to the broker.
215287 Number trackedManagementFeeDelta;
216288
289+ // Indicates if this payment has special handling requirements.
290+ // - none: Regular scheduled payment
291+ // - final: The last payment that closes out the loan
292+ // - extra: An additional payment beyond the regular schedule (overpayment)
217293 PaymentSpecialCase specialCase = PaymentSpecialCase::none;
218294
295+ // Calculates the tracked interest portion of this payment.
296+ // This is derived from the other components as:
297+ // trackedValueDelta - trackedPrincipalDelta - trackedManagementFeeDelta
298+ //
299+ // @return The amount of tracked interest included in this payment that
300+ // will be paid to the vault.
219301 Number
220302 trackedInterestPart () const ;
221303};
222304
223- // This structure describes the difference between two LoanState objects so that
224- // the differences between components don't have to be tracked individually,
225- // risking more errors. How that difference is used depends on the context.
305+ /* Extends PaymentComponents with untracked payment amounts.
306+ *
307+ * This structure adds untracked fees and interest to the base
308+ * PaymentComponents, representing amounts that don't affect the Loan object's
309+ * tracked state but are still part of the total payment due from the borrower.
310+ *
311+ * Untracked amounts include:
312+ * - Late payment fees that go directly to the Broker
313+ * - Late payment penalty interest that goes directly to the Vault
314+ * - Service fees
315+ *
316+ * The key distinction is that tracked amounts reduce the Loan object's state
317+ * (sfTotalValueOutstanding, sfPrincipalOutstanding,
318+ * sfManagementFeeOutstanding), while untracked amounts are paid directly to the
319+ * recipient without affecting the loan's amortization schedule.
320+ */
321+ struct ExtendedPaymentComponents : public PaymentComponents
322+ {
323+ // Additional management fees that go directly to the Broker.
324+ // This includes fees not part of the standard amortization schedule
325+ // (e.g., late fees, service fees, origination fees).
326+ // This value may be negative, though the final value returned in
327+ // LoanPaymentParts.feePaid will never be negative.
328+ Number untrackedManagementFee;
329+
330+ // Additional interest that goes directly to the Vault.
331+ // This includes interest not part of the standard amortization schedule
332+ // (e.g., late payment penalty interest).
333+ // This value may be negative, though the final value returned in
334+ // LoanPaymentParts.interestPaid will never be negative.
335+ Number untrackedInterest;
336+
337+ // The complete amount due from the borrower for this payment.
338+ // Calculated as: trackedValueDelta + untrackedInterest +
339+ // untrackedManagementFee
340+ //
341+ // This value is used to validate that the payment amount provided by the
342+ // borrower is sufficient to cover all components of the payment.
343+ Number totalDue;
344+
345+ ExtendedPaymentComponents (
346+ PaymentComponents const & p,
347+ Number fee,
348+ Number interest = numZero)
349+ : PaymentComponents(p)
350+ , untrackedManagementFee(fee)
351+ , untrackedInterest(interest)
352+ , totalDue(
353+ trackedValueDelta + untrackedInterest + untrackedManagementFee)
354+ {
355+ }
356+ };
357+
358+ /* Represents the differences between two loan states.
359+ *
360+ * This structure is used to capture the change in each component of a loan's
361+ * state, typically when computing the difference between two LoanState objects
362+ * (e.g., before and after a payment). It is a convenient way to capture changes
363+ * in each component. How that difference is used depends on the context.
364+ */
226365struct LoanStateDeltas
227366{
367+ // The difference in principal outstanding between two loan states.
228368 Number principal;
369+
370+ // The difference in interest due between two loan states.
229371 Number interest;
372+
373+ // The difference in management fee outstanding between two loan states.
230374 Number managementFee;
231375
376+ /* Calculates the total change across all components.
377+ * @return The sum of principal, interest, and management fee deltas.
378+ */
232379 Number
233380 total () const
234381 {
235382 return principal + interest + managementFee;
236383 }
237384
385+ // Ensures all delta values are non-negative.
238386 void
239387 nonNegative ();
240388};
0 commit comments