Skip to content
This repository has been archived by the owner on Jun 23, 2022. It is now read-only.

Sketch out stripe integration #1

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

Sketch out stripe integration #1

wants to merge 3 commits into from

Conversation

tmcw
Copy link

@tmcw tmcw commented Apr 11, 2021

Stripe-powered subscriptions for Blitz! Opening this up for folks to check out and review for themselves and maybe point out Blitz best practices that I'm not using yet.

This tries to capture the most important moving parts: subscribing, managing billing, receiving webhooks. It does not include any style.

image

Things I'd love to know:

  • If folks are interested in one-off payments, integrating them into the same codebase. Seems pretty doable to me! My need for this is subscription-oriented, so it's starting with subscriptions.
  • This doesn't support multiple price points, but it definitely could! That's maybe in scope for what I need, so I might build it.
  • Authorization that prevents people without active subscriptions from accessing pages. Subscription status is stored, after being received via webhook, in the database, so this should be doable quite clearly.
  • This includes a whole bunch of migrations. I should squash them into one somehow. TBD on how to do that. Squashed into a single migration, thx flybayer!
  • Is there a more graceful way to require environment variables than what I'm doing of centrally requiring and then re-exporting them? Now using envsafe, thank you flybayer!
  • subscriptionStatus would be a good candidate for an enum, but I think it's nice that this can use the sqlite database for testing simplicity.

Stripe wizard questions:

  • Should you only create a stripe customer for users with subscriptions or create them right off the bat, on user signup?

Completeness questions:

  • I think this should do transactional mail too, or I should read up to see if Stripe can do that.

What's in it:

  • mutations/createCheckoutSession. Creates a stripe user, associates it with the blitz user. Creates a stripe session, returns the ID of that session so the UI can redirect to the payment portal.
  • mutations/customerPortal. Assuming an existing stripe user, provides the URL of the stripe customer portal.
  • integrations/stripe. Centrally configures stripe, re-exports essential environment variables.
  • api/webhook. Receives webhooks from Stripe.
  • pages/success, pages/cancelled - basic success/cancelled pages
  • Database changes: adds price, stripeCustomerId, and subscriptionStatus columns to the User model.

FAQ:

  • Why Blitz? Well, it can't be just Next.js or something on that level because this involves a database and users and I don't want to build that stuff from scratch or throw a dart at some implementation. It could be Redwood etc but I just like Blitz's approach to APIs better than GraphQL-based approaches, so… Blitz.

@tmcw tmcw force-pushed the stripe branch 12 times, most recently from 83baf4f to 7edef57 Compare April 12, 2021 21:29
@flybayer
Copy link

Cool, thanks for sharing!

This includes a whole bunch of migrations. I should squash them into one somehow. TBD on how to do that.

You can nuke all the migrations and then run prisma migrate again and it'll create one clean migration file.

Is there a more graceful way to require environment variables than what I'm doing of centrally requiring and then re-exporting them?

Use something like https://github.com/KATT/envsafe

Also I would add subscription status in the session publicData.

Copy link

@michaelminhvo michaelminhvo left a comment

Choose a reason for hiding this comment

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

A more general pattern is to have subscriptions as its own table: subscription: { name, user_id, subscription_id, price, starting_date, end_date, is_active, other_stripe_fields, currency, billing_frequency}.

Then we can more easily support features such as: 1) price changes, 2) plan cancellation, 3) tracking plan pauses, 4) reporting.

Standard practice is to have price in cents with integer type and a currency column would be useful too.

@tmcw
Copy link
Author

tmcw commented Apr 20, 2021

@michaelminhvo is that what you do in apps? I'm not totally sure if it's better or worse to do that, because then instead of the Stripe API being the single source of truth about subscription data, there's a copy on the application's end which could fall out of date - and the need to try and model stripe's data model into your application (like with a grab-bag untyped other_stripe_fields column)

@michaelminhvo
Copy link

michaelminhvo commented Apr 20, 2021

@michaelminhvo is that what you do in apps? I'm not totally sure if it's better or worse to do that, because then instead of the Stripe API being the single source of truth about subscription data, there's a copy on the application's end which could fall out of date - and the need to try and model stripe's data model into your application (like with a grab-bag untyped other_stripe_fields column)

This is standard practice in payments industry and what we did at our company. A few reasons to do it this way:

  • How would you be able to offer a yearly discounted plan price and a monthly plan price? How would you model that a user is currently on the monthly plan and halfway through the month switched to a yearly plan? There's a lot of benefit in tracking version history ourselves. A user having multiple subscriptions supports more use cases.
  • For example, we eventually supported multiple payment processors. We switched most people from braintree to stripe but at different dates.

It's also standard to have a "payments" table that tracks paid invoices.

Sorry the "other_stripe_fields" was meant as add any other stripe field that was pertinent to your use case. I didn't mean to suggest to dump all the other stripe fields there.

@flybayer
Copy link

Imo, duplicating plan info in your DB is more an old-school approach. Everyone I know that used that approach, including folks who made popular abstraction libraries around that approach, have changed to only cache stripe data in your DB if you need it performantly in real time.

How would you be able to offer a yearly discounted plan price and a monthly plan price? How would you model that a user is currently on the monthly plan and halfway through the month switched to a yearly plan? There's a lot of benefit in tracking version history ourselves. A user having multiple subscriptions supports more use cases.

Stripe seamless handles this now days

@agustif
Copy link

agustif commented Apr 22, 2021

Stripe-powered subscriptions for Blitz! Opening this up for folks to check out and review for themselves and maybe point out Blitz best practices that I'm not using yet.

This looks super interesting, thanks for sharing.

Things I'd love to know:

  • If folks are interested in one-off payments, integrating them into the same codebase. Seems pretty doable to me! My need for this is subscription-oriented, so it's starting with subscriptions.

I'm very much interested in seeing this through, I'm barely migrating my projects from next to blitz, but would love to see this and could offer my help if you don't need it yourself but are OK with merging a PR/ providing guidance on how to code it. Makes sense to have a full stripe bundle one can use as it see fits for each project, lots of parts can be shared.

Stripe wizard questions:

  • Should you only create a stripe customer for users with subscriptions or create them right off the bat, on user signup?

What about on checkout? Maybe free users can have a user db account but not a stripe one necessarily, although I think you can also offer free tiers which makes easier to then upgrade the user to paid from customer portal, makes sense then to prob create the user on user signup, and just assign them on your free tier as a free customer?

Completeness questions:

  • I think this should do transactional mail too, or I should read up to see if Stripe can do that.

Stripe can send email invoices I think you can enable it in your settings. not sure about which API's the offer for such, but prob you can send a param to sendEmailInvoice when generating an invoice (Although I don't know how that works for subscriptions), anyway, it would be good to either externalize to stripe or handle this.

Anyway thank you and will be following what's up with this. Hope it lands as an official stripe plugin for blitz some day.

@agustif
Copy link

agustif commented Apr 24, 2021

Hey what's up. I tried running the code from stripe branch.

Some feedback/stuff I run into:

  • I was in old plan_ api, not sure how of my problems stem from it.
  • I cannot get the status to get to update, at first I wasn't setting up webhooks secret properly. Didnt read the readme properly so totally on me heh, stripe listen duh!
  • Now I do, but still no work, looking at the code I don't see where webhooks' api is called, or where subscriptionStatus is actually retrieved from Stripe/updated on db.

What I mean by no work, is I can get everything up and running with no errors, but once I go -> Back to home, after successfully completing stripe's checkout, nothing is updated on my user status (keeps at incomplete). In your screenshot you appear as subscribed, so Idk what Im missing, maybe your screenshot is more of a this is how it should look when the PR is merged, than a this is how it looks now?

Sorry for the trouble!

Prob this things are not done yet, or are to be in different future PR/commits anyways.

Thx for sharing, I am working on implementing stripe with blitz, so glad to see how someone else is doing it for subscriptions.

@tmcw
Copy link
Author

tmcw commented Apr 24, 2021

I was in old plan_ api, not sure how of my problems stem from it.

For debugging, I really just want to fix this happy path, so hewing exactly to the readme is the way to go. I haven't tested this with the plan_ api and can't guess at whether it'd work or not.

It sounds like webhooks aren't being delivered: the debugging route is just to figure out what is different from your configuration versus what's in the readme. You need stripe listen, a correct webhook signing key, and all of the correct values for secrets. If you can identify an error message anywhere, that'd be helpful, but if there aren't errors, there's not much I can do debugging from afar: it's probably a configuration or stripe-setup mistake.

@agustif
Copy link

agustif commented Apr 24, 2021

I was in old plan_ api, not sure how of my problems stem from it.

For debugging, I really just want to fix this happy path, so hewing exactly to the readme is the way to go. I haven't tested this with the plan_ api and can't guess at whether it'd work or not.

It sounds like webhooks aren't being delivered: the debugging route is just to figure out what is different from your configuration versus what's in the readme. You need stripe listen, a correct webhook signing key, and all of the correct values for secrets. If you can identify an error message anywhere, that'd be helpful, but if there aren't errors, there's not much I can do debugging from afar: it's probably a configuration or stripe-setup mistake.

Hey thank's for the prompt reply tom, don't worry, I will try now again following the README but updating my api_version to the latest and removing all test_data from stripe first. I was trying with a new product plan starting with a price_ now but still.

Also I see on example there's postgres url but on prisma.schema it's sqlite, my question is postgres required for this to work or shouldn't matter?

Will report back whit some code or errors or something later when I have 'em. sorry, and thanks again for offering to help!

@tmcw
Copy link
Author

tmcw commented Apr 24, 2021

Now I do, but still no work, looking at the code I don't see where webhooks' api is called, or where subscriptionStatus is actually retrieved from Stripe/updated on db.

The webhook api works ilke:

  1. You use stripe-cli to forward requests to /api/webhook
  2. Blitz handles this with the api/webhook.ts route, which updates the database - https://github.com/placemark/blitz-saas/blob/stripe/app/api/webhook.ts

I've been developing this exclusively on sqlite - where is a postgres url?

@agustif
Copy link

agustif commented Apr 24, 2021

Now I do, but still no work, looking at the code I don't see where webhooks' api is called, or where subscriptionStatus is actually retrieved from Stripe/updated on db.

The webhook api works ilke:

  1. You use stripe-cli to forward requests to /api/webhook
  2. Blitz handles this with the api/webhook.ts route, which updates the database - https://github.com/placemark/blitz-saas/blob/stripe/app/api/webhook.ts

I've been developing this exclusively on sqlite - where is a postgres url?

So sorry, I had just cloned the whole blitz repo in the same path and that was what showing up in my IDE, so nevermind the postgres stuff.
Trying now after deleting my test data on stripe, hopefully will work will report back !

Thanks for all the help, much appreciated!

@agustif
Copy link

agustif commented Apr 24, 2021

Yayy! Got it to work...
Captura de pantalla 2021-04-24 a las 18 27 58

So it turns out I wasn't reading the stripe-cli docs properly and wasn't adding any --forward-to argument to my stripe listen command duh! Also then I failed by trying to stripe listen --forward-to http://localhost:3000, which is not a POST'able webhook api endpoint obviously in retrospect, but I'm a newbie soo.. I could see the payment go through in the cli, and figured out I neded to post to the /api/webhook endpoint
stripe listen --forward-to http://localhost:3000/api/webhook and all went well.

Thanks for helping me through this! It works, will work on my paymentIntents/checkout now.

@roshan-sama
Copy link

Hey folks, I'll review the files we have so far and suggest what I have, and after discussion, we can choose to merge stuff in. I actually don't have subscriptions, but I'm rather selling virtual products, and when people choose to buy a certain quantity (e.g. 5 lists), they get that many lists added to their account. I think the integration file would make sense to be added, but the way I handle products etc may not if we're not actually selling products?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants