Implementing a Saas with Stripe

Whats the best way to implement something like a SaaS with a subcription plan for users using Stripe which has private scope for premium plans.
Something like ( https://github.com/RailsApps/rails-stripe-membership-saas )

I have no background in Ruby so I don’t know the best way to do this with a controller using either Guardian + Stripity_stripe

Thanks

1 Like

Typically, you’d enforce this type of sign up flow:

  • Account signs up, providing account owner email + password
  • Account owner then selects a plan from list of plans
  • Account owner enters credit card information to pay for the plan
  • Stripe handles monthly subscription billing from that point on
  • Your app receives webhooks from Stripe indicating if a subscription’s billing was successful or not.

So I think you’re on the right track with Guardian/Uberauth and Stripity-stripe. What you should also look into is Stripe Checkout, and Stripe’s webhooks.

I know you said you don’t have any experience with Ruby, but I wrote a book called Multitenancy with Rails (https://leanpub.com/multi-tenancy-rails-2) which provides a walkthrough in the last chapter for setting up subscriptions with Stripe. Perhaps if you read that you could then figure out how to accomplish the same thing in Elixir?

7 Likes

Do you know of any elixir specific guides for handling stripe’s webhooks?

I would imagine you’d have a controller to receive the requests and then probably a module that would pattern match on the request params to determine what to do. I don’t have an example in front of me atm, but I think you would be able to figure it out.

1 Like

I added a route to accept webhooks, but I am getting errors from CSRF protection, and I’m not sure what the canonical and secure solution to that would be. After temporarily disabling CSRF protection, I was able to get a web hook test response, but it returned an error, No signatures found matching the expected signature for payload.

I was following https://hexdocs.pm/stripity_stripe/Stripe.Webhook.html (v1.6.0) and tried this example https://github.com/code-corps/stripity_stripe/issues/245.

Here’s how I structured my solution using stripity stripe, I wrote it around the beginning of the year with phx 1.2 and haven’t revisited the code so I’m not sure of any breaking changes.

Router:

scope "/webhook", David do
    pipe_through :api
    resources "/", WebhookController, only: [:index, :create]
  end 

Controller:

def index(conn, _params) do
    conn
    |> put_status(200)
    |> json("hello")
end

def create(conn, params) do
    case Stripe.Events.get params["id"] do
        {:ok, event} ->
            case event.type do
                "invoice.payment_succeeded" ->
                    user = Repo.get_by(User, card_token: event.data["object"]["customer"])
                    case user do
                        nil ->
                            conn
                            |> put_status(200)
                            |> json("received")
                        _ ->
                            conn
                            |> put_status(200)
                            |> json("renewed")
                        end
                "customer.subscription.deleted" ->
                    user = Repo.get_by(User, card_token: event.data["object"]["customer"])
                    David.Subscription.hook_cancel(user, David.Repo)
                    IO.inspect "#{user.email} Found and deleted"
                    conn
                    |> put_status(200)
                    |> json("received")
                _ ->
                    conn
                    |> put_status(200)
                    |> json("received")
                end
        {:error, message} ->
            conn
            |> put_status(403)
            |> json(message)
    end
end

def hook_cancel(user, repo) do
    user
    |> User.nonpass_changeset
    |> put_change(:sub_id, nil)
    |> put_change(:card_token, nil)
    |> put_change(:membership, "Free")
    |> repo.update
end

Hope this helps.

1 Like

You probably want to disable CSRF for that particular route, and then set an endpoint secret on stripe.
When you receive the webhook call on your controller, use the Stripe.Webhook.construct_event/3 to verify the post.

In Rails I used this:

def webhooks
    payload = request.body.read
    sig_header = request.env['HTTP_STRIPE_SIGNATURE']
    endpoint_secret = ENV['STRIPE_WH_SECRET']
    begin
      event = Stripe::Webhook.construct_event(
        payload, sig_header, endpoint_secret
      )
    rescue JSON::ParserError => e
      head 403
      return
    rescue Stripe::SignatureVerificationError => e
      head 403
      return
    end
    pl = JSON.parse(payload)
    SubscriptionManageWorker.perform_async(pl)
    head :ok
end

It should be similar with Stripity, and probably you can write it more concisely

1 Like

thank you! that definitely helped me in the right direction.