feat: add skip_proration flag to first-payment endpoint and checkout #470
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!470
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "458-skip-proration"
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_prorationparam to the first-payment blast endpoint andprorateparam to the checkout route so custom-deal players get charged their exact monthly fee.Changes
src/basketball_api/routes/checkout.py—proratequery param onGET /first-payment; when false, chargesmonthly_fee * 100cents directlysrc/basketball_api/routes/admin.py—skip_prorationquery param onPOST /email/first-payment, forwarded to send functionsrc/basketball_api/services/email.py—skip_prorationkwarg onsend_first_payment_email(); adjusts fee display, hides proration text, appends&prorate=falseto checkout URLTest Plan
Review Checklist
Related Notes
sop-contract-offer— contract offer SOPsop-email-send— email approval workflowproject-westside-basketball— parent projectCloses #458
PR #470 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy -- applying PEP, OWASP, and FastAPI-specific checks.
Architecture: The change adds a
skip_prorationboolean flag that flows cleanly through three layers: admin blast endpoint -> email service -> checkout redirect. The parameter naming is consistent (skip_prorationon the admin/email side,prorateon the checkout URL side -- inverse semantics but matches the consumer-facing vs. admin-facing intent).Input validation: Both new parameters are typed
boolwith safe defaults (Falseforskip_proration,Trueforprorate). FastAPI/Pydantic handles coercion. No injection risk.Keyword-only arg: Good use of
*to forceskip_prorationas keyword-only insend_first_payment_email(). This prevents positional-arg mistakes at call sites.Checkout amount calculation: When
prorate=False, the amount ismonthly_fee * 100which correctly converts dollars to cents, consistent with the existing_calculate_prorated_centsreturn type.BLOCKERS
1. No test coverage for new functionality.
Three new behaviors were introduced with zero test coverage:
GET /checkout/first-payment?prorate=false-- no test verifies that passingprorate=falseskips proration and chargesmonthly_fee * 100cents. The existing test attests/test_first_payment.py:87(test_valid_token_redirects_to_stripe) only tests the default (prorated) path.POST /admin/email/first-payment?skip_proration=true-- no test verifies that theskip_prorationparam is forwarded tosend_first_payment_email. The mock intests/test_first_payment_blast.py:100patchessend_first_payment_emailbut never asserts on theskip_prorationkwarg.send_first_payment_email(..., skip_proration=True)-- no test verifies the email HTML omits proration text, usesfee_label, or appends&prorate=falseto the checkout URL. Tests intests/test_first_payment_email.pyonly call without the kwarg.This is a BLOCKER per review criteria: new functionality with zero test coverage. At minimum, need:
prorate=falseassertingamount_cents == monthly_fee * 100skip_proration=Trueasserting the HTML contains "April fee" (not "Prorated"), omits the "Full monthly fee" line, and the checkout URL contains&prorate=falseskip_proration=trueasserting the kwarg is forwarded2. Plain-text body label bug.
In
src/basketball_api/services/email.py, the plain-text fallback body still hardcodes "Prorated April fee" on the line:When
skip_proration=True, this should say "April fee" (matching the HTMLfee_label), not "Prorated April fee." The HTML template correctly uses{fee_label}but the plain-text version does not. This is a correctness bug -- parents receiving the plain-text email with a custom deal will see "Prorated April fee: $200" which is misleading (it is not prorated).NITS
Double newline in plain text: The expression
chr(10) if not skip_proration else ''followed by\non the next f-string produces\n\nwhen proration is shown. This won't cause user-visible issues in email clients but is slightly messy. Consider using a cleaner conditional block.Hardcoded month name "April": Both the subject line and
fee_labelreference "April." This will need updating next month. Consider deriving fromdatetime.now()or at least adding a code comment noting it is intentionally hardcoded for this blast cycle.Subject line unchanged for custom deals: When
skip_proration=True, the subject still says "Great First Practice" which may not apply to custom-deal players who have already been practicing. This is a product decision, not a code defect -- flagging for awareness.SOP COMPLIANCE
458-skip-prorationPROCESS OBSERVATIONS
Small, focused change (26 additions) with clean layering through three files. The architecture is sound. The gap is purely test coverage and the plain-text label bug. Adding tests should be straightforward since the existing test patterns (
test_first_payment.py,test_first_payment_email.py,test_first_payment_blast.py) already provide fixtures and mock patterns that just need askip_proration=True/prorate=falsevariant.VERDICT: NOT APPROVED
Two blockers must be resolved:
prorate=false, emailskip_proration=True, blast forwarding)fee_labelinstead of hardcoded "Prorated April fee"PR #470 Re-Review
DOMAIN REVIEW
Python/FastAPI/SQLAlchemy. Three files changed across routes and services, plus three new test files. The feature adds a
skip_prorationboolean flag through the admin blast endpoint, email service, and checkout route so custom-deal players get charged their exact monthly fee instead of the prorated amount.Previous blockers (from first review):
tests/test_first_payment.py,tests/test_first_payment_blast.py, andtests/test_first_payment_email.py. Tests cover: checkout withprorate=falseverifying exact cents (5000 not 4000), blast forwardingskip_prorationkwarg, and email service content assertions (fee label, checkout URL param, plain text wording).fee_labelvariable ("April fee"vs"Prorated April fee") in both HTML and plain text. The "Full monthly fee" comparison line and the proration explanation paragraph are conditionally omitted viaskip_proration.Code quality observations:
*separator onskip_prorationparam insend_first_payment_email()-- good defensive API design, prevents positional arg mistakes.proratequery param defaults toTrueon the checkout route, preserving backward compatibility for existing links.skip_prorationdefaults toFalseon the admin endpoint, also backward-compatible.&prorate=falseto checkout URL only when skipping proration, ensuring the checkout route and email stay in sync.BLOCKERS
None. Both previous blockers are resolved.
NITS
HTML body still shows "Since you're joining partway through April" paragraph unconditionally (
/home/ldraney/basketball-api/src/basketball_api/services/email.py~line 1072-1074). Whenskip_proration=True, this proration explanation is misleading since the player is being charged the full fee. The plain-text body correctly omits this paragraph, but the HTML version does not. Consider wrapping it in the same conditional.chr(10)usage in f-string conditionals in the plain_body construction is functional but hard to read. A multilineif/elseblock building the plain_body string would be clearer. Not blocking since the test assertions validate the output.Extra blank line in plain_body when
skip_proration=True-- the linef"{'Full monthly fee: $' + str(monthly_fee) + chr(10) if not skip_proration else ''}\n"produces a bare\nwhen skipping, yielding a double blank line. Minor formatting issue in the plain-text fallback.PR body says "3 files changed, 22 insertions, 8 deletions" but the actual diff shows 6 files changed, 123 insertions, 10 deletions. The review checklist was not updated after tests were added.
SOP COMPLIANCE
458-skip-prorationfollows{issue-number}-{kebab-case-purpose}conventionPROCESS OBSERVATIONS
mergeable: falsereported by Forgejo -- may need a rebase before merge.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.