Skip to content

Separate out payment methods - #747

Open
mitchelljkotler wants to merge 6 commits into
masterfrom
separate-out-payment-methods
Open

Separate out payment methods#747
mitchelljkotler wants to merge 6 commits into
masterfrom
separate-out-payment-methods

Conversation

@mitchelljkotler

@mitchelljkotler mitchelljkotler commented Jul 22, 2026

Copy link
Copy Markdown
Member

This pulls payment methods (credit cards and bank accounts) out into their own model. This helps organize the data and opens the path to supporting multiple payment methods in the future.

@mitchelljkotler
mitchelljkotler marked this pull request as draft July 22, 2026 18:07
@allanlasser
allanlasser temporarily deployed to squarelet-pi-separate-o-vc1dxs July 22, 2026 18:07 Inactive
@mitchelljkotler
mitchelljkotler force-pushed the separate-out-payment-methods branch from 3175d8a to ef9e135 Compare July 22, 2026 19:24
@mitchelljkotler
mitchelljkotler temporarily deployed to squarelet-pi-separate-o-vc1dxs July 22, 2026 19:24 Inactive
@mitchelljkotler
mitchelljkotler temporarily deployed to squarelet-pi-separate-o-vc1dxs July 22, 2026 19:53 Inactive
@mitchelljkotler
mitchelljkotler marked this pull request as ready for review July 22, 2026 20:05

@allanlasser allanlasser left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There are 4 lifecycle events from Stripe that we should handle for payment methods, if we aren't already:

Comment on lines +170 to +216
def default_payment_method_obj(self):
"""Return the default PaymentMethod object, or None.

Caches the result on the instance so multiple property
accesses in the same request only hit the DB once. The
cache is cleared automatically by ``save_payment_cache``
and ``clear_payment_cache``.
"""
sentinel = object()
cached = getattr(self, "_default_pm_cache", sentinel)
if cached is not sentinel:
return cached
result = self.payment_methods.filter(is_default=True).first()
self._default_pm_cache = result
return result

def _invalidate_pm_cache(self):
try:
del self._default_pm_cache
except AttributeError:
pass

@property
def payment_brand(self):
pm = self.default_payment_method_obj()
return pm.brand if pm else ""

@property
def payment_last4(self):
pm = self.default_payment_method_obj()
return pm.last4 if pm else ""

@property
def payment_exp_month(self):
pm = self.default_payment_method_obj()
return pm.exp_month if pm else None

@property
def payment_exp_year(self):
pm = self.default_payment_method_obj()
return pm.exp_year if pm else None

@property
def stripe_payment_method_id(self):
pm = self.default_payment_method_obj()
return pm.stripe_id if pm else ""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like how you preserve the current API here!

Comment thread squarelet/organizations/models/payment.py
@allanlasser allanlasser linked an issue Jul 23, 2026 that may be closed by this pull request
@allanlasser

Copy link
Copy Markdown
Member

We don't think we need any new UI, on top of what we've done in #716. We just need to update templates/context in this PR to make sure the data flows through the same as before.

@allanlasser allanlasser added this to the Payments & Plans milestone Aug 4, 2026
@mitchelljkotler

Copy link
Copy Markdown
Member Author

There are 4 lifecycle events from Stripe that we should handle for payment methods, if we aren't already:

* [`payment_method.attached`](https://docs.stripe.com/api/events/types#event_types-payment_method.attached)

* [`payment_method.automatically_updated`](https://docs.stripe.com/api/events/types#event_types-payment_method.automatically_updated)

* [`payment_method.detached`](https://docs.stripe.com/api/events/types#event_types-payment_method.detached)

* [`payment_method.updated`](https://docs.stripe.com/api/events/types#event_types-payment_method.updated)

These are being handled now

Comment thread squarelet/organizations/tasks.py Outdated
stripe_customer = customer_svc.retrieve(customer_id)
invoice_settings = getattr(stripe_customer, "invoice_settings", None)
default_pm_id = invoice_settings and invoice_settings.get("default_payment_method")
if default_pm_id != stripe_id:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure I follow the logic here. If the payment method is being attached, what's the likelihood it's the default payment method? Even though we can support multiple payment methods, we're throwing this away and creating inconsistency between Stripe and Accounts.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Although the data model supports saving multiple payment methods, this isn't exposed or used anywhere. We could save the data locally to keep things in sync, but don't want to increase the scope to include full multiple payment method handling support at this time.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added support to save non default payment methods for data syncing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Separate out Payment Method details into its own model

2 participants