-
Notifications
You must be signed in to change notification settings - Fork 652
feat: implement member model support for customer seats #8789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
4552491 to
bd66cf4
Compare
| if member_model_enabled: | ||
| # NEW PATH: Keep customer_id (billing customer), clear member_id and email | ||
| seat.member_id = None | ||
| seat.email = None | ||
| else: | ||
| # OLD PATH: Clear customer_id (seat member customer) | ||
| seat.customer_id = None | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can simplify this by setting the same attributes to None. It should have no downside and will simplify the code
| # OLD PATH: member_model_enabled = False (backward compatible) | ||
| # Create Customer for seat member |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we simplify these 2 if conditions?
| # Get product info and organization from either subscription or order | ||
| if seat.subscription_id and seat.subscription and seat.subscription.product: | ||
| organization_id = seat.subscription.product.organization_id | ||
| organization = seat.subscription.product.organization |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure that the organization is eager loaded in the order and product
| Returns: | ||
| Member entity for the seat member | ||
| """ | ||
| from polar.member.repository import MemberRepository |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@claude update the CLAUDE.md to mention that imports should always be on top
eff5104 to
3ac05c0
Compare
3823916 to
0468eac
Compare
cdac7e7 to
2a67a1a
Compare
0468eac to
9ca018e
Compare
2a67a1a to
a66ec86
Compare
9ca018e to
45ecc3d
Compare
45ecc3d to
9caf079
Compare
Decouple seat members from Customer entities by adding a member_model_enabled feature flag. When enabled, Members exist independently under the billing customer (purchaser), with customer_id on CustomerSeat referencing the purchaser rather than the seat occupant. Includes new email column on CustomerSeat for pending invitations. 🤖 Generated with Claude Code Co-Authored-By: Claude Haiku 4.5 <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
The test was expecting seat.customer_id to equal the seat member's customer, but with member_model_enabled=True, seat.customer_id should equal the billing customer (subscription owner). Updated assertions to match the actual implementation behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Simplify revoke_seat by clearing all identifiers unconditionally - Combine validation conditions into single check in assign_seat - Add Order.product.organization eager loading in repository - Move MemberRepository import to top of file - Add imports guideline to CLAUDE.md - Add tests for member_model_enabled flows: - test_claim_seat_with_member_model_enabled - test_revoke_seat_with_member_model_enabled - test_resend_invitation_with_member_model_enabled - test_assign_seat_rejects_customer_id_when_member_model_enabled - test_assign_seat_requires_email_when_member_model_enabled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
Introduce SeatAssignmentTarget dataclass to unify the seat creation logic. The only branching point is now resolving the target (who the seat is for), while seat creation, token generation, and notifications are unified. Changes: - Add SeatAssignmentTarget dataclass with customer_id, member_id, email, seat_member_email - Add _resolve_member_model_target() for member_model_enabled=True path - Add _resolve_legacy_target() for member_model_enabled=False path - Refactor assign_seat() to use unified seat creation logic - Simplify claim_seat() to share validation and claim logic between paths This reduces code duplication and makes the feature flag logic clearer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
9caf079 to
75cc4b8
Compare
📋 Summary
Related Issue: Fixes #8784
Implements the member model for customer seats, decoupling seat members from Customer entities. When the
member_model_enabledfeature flag is enabled, Members are created independently under the billing customer (purchaser), and CustomerSeat.customer_id references the purchaser rather than the seat occupant.🎯 What
member_model_enabledfeature flag (organization-level setting)emailcolumn to CustomerSeat for storing pending invitation email addressesmember_idfield to CustomerSeat schema🤔 Why
This change enables organizations to manage seat members without creating a Customer record for each one, reducing database bloat and improving permission management. Seat members become team members of the billing customer's organization instead of independent customers.
🔧 How
The implementation uses two parallel code paths:
The feature flag is checked in all critical methods: assign_seat, claim_seat, revoke_seat, resend_invitation
🧪 Testing
📝 Additional Notes
Validation was added to reject
customer_idandexternal_customer_idparameters whenmember_model_enabled=true, since these are not supported in the new model.🤖 Generated with Claude Code