When taking payments though stripe in my Phoenix application, the posting of the credit card information goes directly to the stripe service for PCI reasons. It works this way regardless of the backend for good reason, but this means storing the billing information for an account or user is not a normal CRUD operation.
Assuming my billing schema looks like this (below), what would be the preferred way to handle the interaction to stripe? In the controller, in the changeset using virtual fields or wrap the stripe interaction into a larger function that gets called from the controller.
Handling it in the changeset looks appealing but this is a lot of interaction that is just tangentially related to storing the billing information.
schema "billings" do
field :customer_id, :string
field :payment_id, :string
field :card_brand, :string
field :card_exp_month, :integer
field :card_exp_year, :integer
field :card_last4, :string
field :card_updated, :utc_datetime
belongs_to :user, Testapp.Accounts.User
field :subscription_id, :string
field :subscription_period_end, :utc_datetime
field :subscription_period_start, :utc_datetime
field :subscription_status, :string
field :subscription_type, :string, virtual: true
Putting the Stripe interaction into the changeset is definitely mixing levels of abstraction some, it’s likely going to lead to complicated code.
I recommend extracting the Stripe steps to a function that takes the
setupIntent data from params and returns a struct that captures key data (
The controller can then be responsible for composing that function with appropriate Ecto plumbing (either directly or in a context) to do the API interaction and persist the results.
Also consider what data in
billings you’ll want to poll for updates (or plan to listen to webhooks about); for instance, some processors (for some issuers) can handle “card updates”, where when your old payment card expires the processor automatically updates the card on your subscription - causing
card_exp_month and so on to change.
Yes, that makes sense. I had another suggestion to move bulk of the work to a function of a Phoenix context module, so seems like the consensus is to have the interaction with Stripe in a separate function and not in the changeset.
I do have a webhook route and controller that will take updates from stripe, that part is already cleaner because it’s updating information that came from stripe to begin with.
Thanks for the help.