Skip to content

Fix: Round amount_finaid() to nearest cent to unblock percentage-aid payments - #6084

Open
Shresth-14 wants to merge 4 commits into
learning-unlimited:mainfrom
Shresth-14:fix-finaid-rounding-6055
Open

Shresth-14 wants to merge 4 commits into
learning-unlimited:mainfrom
Shresth-14:fix-finaid-rounding-6055

Conversation

@Shresth-14

Copy link
Copy Markdown
Contributor

Percentage-based financial aid grants on non-whole-dollar amounts (e.g. 25% of $47.50 = $11.875) produce fractional cents in amount_finaid(). This makes amount_due() a non-integer number of cents, causing the Stripe payment flow to fail with an inconsistent_amount error because int(amount_due * 100) disagrees with Decimal(amount_due) * 100.

Round amount_finaid() using ROUND_HALF_UP per maintainer guidance so that all downstream accounting calculations resolve to whole cents. Add regression tests covering fractional cents and half-cent rounding.

Description

When financial aid is awarded as a percentage discount (e.g., 25%) and the program cost or item cost is not a whole-dollar amount (e.g., $47.50), IndividualAccountingController.amount_finaid() calculated fractional-cent aid amounts without rounding ($11.875).

This produced a cascading failure when the student attempted to pay online:

  1. amount_due() returned fractional cents ($35.625).
  2. payonline() computed the Stripe form amount as int(Decimal('35.625') * 100) = 3562 ($35.62).
  3. charge_payment() verified this against Decimal(iac.amount_due()) * 100 = 3562.5.
  4. Because 3562 ≠ 3562.5, charge_payment() aborted with inconsistent_amount, completely blocking the student from completing payment.
  5. In addition, link_paid_transfers() would fail due to the transfers not summing to the target price ($35.62 + $11.875 = $47.495 ≠ $47.50).

Solution

  • Updated IndividualAccountingController.amount_finaid() in esp/esp/accounting/controllers.py to round the final aid amount to whole cents using .quantize(Decimal('0.01'), rounding=ROUND_HALF_UP).
  • Followed maintainer guidance on rounding mode (ROUND_HALF_UP: half a cent or more rounds up to the student's benefit; less than half a cent rounds down).
  • Added 3 regression tests in esp/esp/accounting/tests/test_models.py verifying:
    • Exact issue scenario (25% off $47.50) produces whole cents and consistent payment amounts.
    • Half-cent boundary explicitly rounds up (confirming ROUND_HALF_UP).
    • Combination of dollar-cap grant and percentage discount resolves to whole cents.

Related Issue

Closes #6055

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactor / cleanup
  • CI/CD or build change

Testing

  • Existing tests pass
  • New tests added (if applicable)
  • Manually verified

Testing Details:

  • New tests added:
    • IndividualAccountingControllerTest.test_amount_finaid_percentage_rounded_to_cents
    • IndividualAccountingControllerTest.test_amount_finaid_half_cent_rounds_up
    • IndividualAccountingControllerTest.test_amount_finaid_percentage_and_dollar_cap_rounded_to_cents
  • Manual Verification:
    • Verified math on test cases ($47.50 at 25% → finaid $11.88, due $35.62; int(due * 100) == int(Decimal(due) * 100) == 3562).

AI Disclosure

This pull request was prepared with the assistance of an AI assistant (Google Antigravity) to identify root cause code paths, implement the rounding fix following maintainer discussion on #6055, and write regression tests. All changes and tests have been reviewed and verified.

Checklist

  • Self-reviewed the code
  • Updated documentation (if needed)
  • No new warnings or errors introduced

…learning-unlimited#6055)

Percentage-based financial aid grants on non-whole-dollar amounts (e.g.
25% of $47.50 = $11.875) produce fractional cents in amount_finaid().
This makes amount_due() a non-integer number of cents, causing the
Stripe payment flow to fail with an 'inconsistent_amount' error because
int(amount_due * 100) disagrees with Decimal(amount_due) * 100.

Round amount_finaid() using ROUND_HALF_UP per maintainer guidance so
that all downstream accounting calculations resolve to whole cents.
Add regression tests covering fractional cents and half-cent rounding.
Copilot AI balanced review requested due to automatic review settings October 3, 2026 19:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The half-up regression test does not distinguish half-up from half-even rounding.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment on lines +495 to +504
# $47.50 at 25% = $11.875 — this is the exact half-cent boundary.
LineItemType.objects.filter(
program=self.program, text='Program admission'
).update(amount_dec=Decimal('47.50'))

self.iac.set_finaid_params(None, 25)
finaid = self.iac.amount_finaid()
# ROUND_HALF_UP: $11.875 → $11.88 (rounds up, not to $11.88 via ROUND_HALF_EVEN too,
# but importantly NOT $11.87 as a truncation or floor would give).
self.assertEqual(finaid, Decimal('11.88'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Students with a percentage financial aid grant can't pay when the price isn't a whole dollar

2 participants