Help with modeling stripe elements flow

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

    timestamps()
  end

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 (customer_id, payment_id etc).

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.

1 Like

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.