feat: cherry-pick first-payment blast + skip_proration to main #469
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!469
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "466-cherry-pick-first-payment"
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
Cherry-picks 3 commits from
369-first-payment-email-blastonto main. Branch was deployed to prod but never merged (#466).Changes
src/basketball_api/services/email.py���send_first_payment_email()withskip_prorationkwarg,_calculate_prorated_fee(), branded HTML emailsrc/basketball_api/routes/admin.py��POST /admin/email/first-paymentblast endpoint withskip_prorationquery paramsrc/basketball_api/routes/checkout.py—GET /checkout/first-paymentroute withproratequery param for flat-fee chargingsrc/basketball_api/models.py—first_paymentadded to EmailType enumtests/test_first_payment_email.py— email send tests including skip_prorationtests/test_first_payment_blast.py— blast endpoint + checkout route testsTest Plan
POST /admin/email/first-payment?skip_proration=truesends flat fee emailGET /checkout/first-payment?token=X&prorate=falsecharges exact monthly_fee * 100 centsReview Checklist
Related Notes
sop-contract-offer— contract offer SOPsop-email-send— email approval workflowproject-westside-basketball— parent projectCloses #466
PR #469 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy / Stripe / Gmail SDK
Cherry-pick conflict resolution: The PR cherry-picks 3 commits from
369-first-payment-email-blastonto main. The PR body acknowledges 1 conflict in email.py and 5 in checkout.py. However, the merge produced a critical duplication inadmin.pythat was not resolved.Changes reviewed:
models.py--first_paymentenum value added toEmailType. Clean.routes/admin.py-- New blast endpoint withskip_prorationsupport. Duplicated (see BLOCKERS).routes/checkout.py--first_payment_checkoutrewritten: returnsCheckoutResponseJSON instead ofRedirectResponse307 redirect, addsproratequery param, removesOrdercreation, removescontract_status == signedfilter, imports_calculate_prorated_feefrom email service.services/email.py--send_first_payment_emailgainsskip_prorationkwarg, email copy updated, conditional proration logic added.tests/test_first_payment_blast.py-- Tests for blast skip_proration flag, checkout route (prorated, flat, invalid token).tests/test_first_payment_email.py-- Tests for email send with/without skip_proration.BLOCKERS
1. Duplicate
admin_send_first_paymentfunction andFirstPaymentBlastResponseclass inadmin.pyThe existing main branch already has
FirstPaymentBlastResponse(line 978) andadmin_send_first_payment(line 984) with@router.post("/email/first-payment"). The cherry-pick appends a SECOND copy of both after line 1021. The second definition:skip_prorationparameter (the new behavior)@router.post("/email/first-payment")again -- duplicate route registrationIn Python, the second class/function definition silently shadows the first. FastAPI will register the route twice, with undefined behavior on which handler receives requests. This is a cherry-pick conflict that was not resolved -- the old version should have been REPLACED, not duplicated.
File:
/home/ldraney/basketball-api/src/basketball_api/routes/admin.py, lines 978-1090 (two copies).2. PR is not mergeable
Forgejo reports
mergeable: false. The PR cannot be merged in its current state. Conflicts must be resolved.3. Removed
contract_status == signedguard in checkout route -- security/business logic concernThe old
first_payment_checkoutfiltered byPlayer.contract_status == ContractStatus.signed. The new version only filters bycontract_token. This means a player who has NOT signed their contract (but has a token assigned) can now access the payment checkout. This may be intentional (to allow payment before contract signing completes), but it is a behavioral change that weakens a business-logic guard. If intentional, it should be documented in the PR body.4. Removed
Ordercreation from checkout flow -- data integrity concernThe old checkout route created an
Orderrecord (withOrderStatus.pending), linked it to the Stripe session viastripe_checkout_session_id, and prevented duplicate orders. The new version creates a Stripe session with NO correspondingOrderrecord. This means:checkout.session.completedevents may expect anorder_idin metadata (the old code setmetadata.order_id; the new code does not)If this is intentional simplification, it needs to be called out and the webhook handler verified. As-is, this risks orphaned Stripe payments with no database record.
NITS
Hardcoded "April" in fee labels (
checkout.pylines in diff:"Prorated April fee","April fee"). The existing codebase has this pattern throughout, so this is consistent -- but it will need updating when May rolls around. Consider a TODO comment or config value.Cross-module import of private function:
_calculate_prorated_fee(underscore-prefixed, conventionally private) is imported fromservices.emailintoroutes/checkout.py. If this is now a shared utility, consider renaming it without the underscore or moving it to a shared module._calculate_prorated_feereceivesplayer.monthly_feedirectly in checkout.py (_calculate_prorated_fee(player.monthly_fee)), and the function handlesNoneinternally (fee = monthly_fee or 200). Meanwhile, the checkout route also doesmonthly_fee = player.monthly_fee or 200separately for the flat-fee path. The default-200 logic is duplicated across two code paths.Test coverage is good: Tests cover skip_proration=true/false for both the blast endpoint and the email service, plus checkout route prorated/flat/invalid-token paths. Well structured.
SOP COMPLIANCE
466-cherry-pick-first-paymentfollows{issue-number}-{kebab-case-purpose}PROCESS OBSERVATIONS
contract_status == signedfilter andOrdercreation were removed. These are significant behavioral changes that need justification.VERDICT: NOT APPROVED
Two critical blockers must be resolved:
FirstPaymentBlastResponseclass andadmin_send_first_paymentfunction in admin.py (keep only the new version withskip_proration)mergeable: falsestatusOrdercreation andcontract_statusguard -- either restore them or document why they were intentionally removed and verify the Stripe webhook handler still worksView 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.