feat: add skip_proration flag to first-payment endpoint and checkout #472
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api!472
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "458-skip-proration-v2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add
skip_prorationto first-payment blast andprorateparam to checkout route. Same logic as QA-approved #470, rebased cleanly on current main.Changes
src/basketball_api/routes/checkout.py—proratequery param onGET /first-paymentsrc/basketball_api/routes/admin.py—skip_prorationquery param onPOST /email/first-paymentsrc/basketball_api/services/email.py—skip_prorationkwarg, dynamic fee label + conditional proration textTest Plan
Review Checklist
Related Notes
sop-contract-offerproject-westside-basketballCloses #458
PR #472 Review
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy / Pydantic
Small, focused change: 19 additions across 3 files. Adds
skip_prorationflag to the admin first-payment blast endpoint andproratequery param to the checkout route. Logic is straightforward -- when skipping proration, charge the full monthly fee instead of the prorated amount.Correctness observations:
Plaintext email label bug -- In
services/email.py, the HTML body correctly uses the dynamic{fee_label}variable ("April fee" vs "Prorated April fee"), but the plaintext fallback still has the hardcoded string"Prorated April fee: ${charge_fee}". Whenskip_proration=True, the plaintext email will incorrectly say "Prorated April fee: $200" instead of "April fee: $200". The fix is to use{fee_label}in the plaintext body too."Covers remaining days" copy is misleading when skipping proration -- Both the HTML body ("Since you're joining partway through April, your first month's fee covers the remaining days in the month.") and the plaintext body have this same proration-specific copy that renders regardless of the
skip_prorationflag. When charging the full $200 flat fee, telling parents the fee "covers the remaining days" is inaccurate. This is a nit since the primary use case is a one-time admin override, but worth noting.FastAPI parameter patterns are correct --
skip_proration: bool = Falseas a query param on POST andprorate: bool = Query(True)on GET are both clean. The keyword-only*separator onsend_first_payment_emailis good practice.Checkout URL construction --
checkout_url += "&prorate=false"is safe because the base URL already contains?token=..., so the ampersand is correct.BLOCKERS
No test coverage for the new
skip_proration/prorate=Falsepaths. There are existing tests for the prorated path (test_first_payment.py,test_first_payment_email.py) but zero tests exerciseskip_proration=Trueon the email service,prorate=Falseon the checkout route, orskip_proration=Trueon the admin blast endpoint. New functionality with zero test coverage is a blocker per review criteria. Needed tests:test_send_first_payment_emailwithskip_proration=True-- verifiescharge_fee == monthly_fee, checkout URL contains&prorate=false, and email label is "April fee" (not "Prorated April fee")test_first_payment_checkoutwithprorate=false-- verifiesamount_cents == monthly_fee * 100(no proration calc)Plaintext email label inconsistency (item 1 above) -- The HTML body uses
{fee_label}but the plaintext body hardcodes "Prorated April fee". This meansskip_proration=Trueproduces an email where HTML says "April fee" but plaintext says "Prorated April fee". This is a functional bug affecting email clients that render plaintext.NITS
skip_prorationin both HTML and plaintext bodies. Not blocking since this is an admin override path, but it reads incorrectly when charging full price.prorateparam on the checkout route is unauthenticated -- any user with a valid token could append&prorate=falseto pay the full fee instead of the prorated amount. Sinceprorate=Falsecharges MORE (not less), this is not a financial risk, but worth documenting the intent.SOP COMPLIANCE
458-skip-proration-v2)sop-contract-offer,project-westside-basketball)PROCESS OBSERVATIONS
This is noted as the same logic from QA-approved PR #470, rebased on current main. The prior approval does not carry forward -- the plaintext label bug (item 1) either existed in #470 and was missed, or was introduced in the rebase. Either way it needs fixing in this PR. The change failure risk is low (small surface area, admin-controlled flag), but the missing tests and plaintext inconsistency must be addressed before merge.
VERDICT: NOT APPROVED
Two blockers: (1) zero test coverage for the new skip_proration/prorate paths, (2) plaintext email label bug where
{fee_label}is not used in the plaintext body. Fix both and re-request review.PR #472 Review
Re-review after fixes for two blockers from previous review.
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy / Stripe / Gmail email service.
Logic correctness: The
skip_proration/prorateflag threads cleanly through all three layers:POST /admin/email/first-payment?skip_proration=truepasses the flag tosend_first_payment_emailsend_first_payment_emailconditionally uses flatmonthly_feevs_calculate_prorated_fee(), builds dynamicfee_label, and appends&prorate=falseto the checkout URLGET /checkout/first-payment?token=X&prorate=falsechargesmonthly_fee * 100cents instead of calling_calculate_prorated_cents()FastAPI typing:
prorate: bool = Query(True)correctly parses string"false"toFalsevia Pydantic coercion. Theskip_proration: bool = Falseon the admin route is a plain query param default, also correct.Keyword-only enforcement: The
*separator beforeskip_prorationinsend_first_payment_emailforces callers to pass it by name. Good defensive pattern.BLOCKERS
None. Both previous blockers are resolved:
Tests added -- Three new tests across three files:
test_first_payment_no_prorate-- verifiesunit_amount == 5000(50 * 100) whenprorate=falsetest_blast_skip_proration_forwarded-- verifies the admin blast endpoint forwardsskip_proration=Trueto the email servicetest_send_first_payment_email_skip_proration-- verifies HTML contains$200(not$165), URL containsprorate=false, plain text uses"April fee"(not"Prorated April fee")Hardcoded label replaced --
fee_labelis now dynamic:"April fee"when skipping proration,"Prorated April fee"otherwise. Used in both HTML and plaintext bodies.NITS
Misleading body copy when skip_proration=True: The paragraph "Since you're joining partway through April, your first month's fee covers the remaining days in the month" is still rendered unconditionally in both HTML (line ~1073) and plaintext (line ~1112). When
skip_proration=True, they are paying the full month -- not "remaining days." Consider making this paragraph conditional or rewording it. Non-blocking since the fee label and amount are correct and the user will see "April fee: $200" which is unambiguous.PR body stats stale: The checklist says "19 insertions, 3 files" but the actual diff is 90 additions across 6 files. Tests were likely added after the PR body was written. Cosmetic only.
SOP COMPLIANCE
458-skip-proration-v2sop-contract-offer,project-westside-basketball)PROCESS OBSERVATIONS
Clean re-review cycle. Both blockers from the first review were addressed directly. The test coverage is thorough -- it validates the flag at each layer (admin route forwarding, email content, checkout amount). Change failure risk is low given the mock-based test approach and the flag defaulting to existing behavior (
prorate=True/skip_proration=False).VERDICT: APPROVED
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.