mirror of
https://github.com/wasp-lang/open-saas.git
synced 2025-11-25 21:49:02 +01:00
refactor Stripe webhook (#200)
* rename TierIds to PaymentPlanIds * refactor webhook and util functions * pass userDelegate to function * Update dbSeeds.ts * update app diff * Update template/app/src/server/stripe/stripeClient.ts Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com> * extract event handlers and more * Update AccountPage.tsx * address filips pro effective typescripting and stuff * Martin's attempt at consolidating types. * fix * fix webhook events and validation * small changes * put stripe event handlers back for marty merge * merge consilidated types from martin * move some types around * add docs for stripe api version * Update AccountPage.tsx * Update stripe.ts * update SubscriptionStatus type * Update actions.ts * add assertUnreachable util * more small changes * Update deploying.md * update accountPage and docs * update app_diff --------- Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com> Co-authored-by: Martin Sosic <sosic.martin@gmail.com>
This commit is contained in:
@@ -7,7 +7,7 @@ banner:
|
||||
---
|
||||
|
||||
This reference will help you understand how the User entity works in this template.
|
||||
This includes the user roles, subscription tiers and statuses, and how to authorize access to certain pages and components.
|
||||
This includes the user roles, subscription plans and statuses, and how to authorize access to certain pages and components.
|
||||
|
||||
## User Entity
|
||||
|
||||
@@ -23,14 +23,15 @@ entity User {=psl
|
||||
isAdmin Boolean @default(false)
|
||||
stripeId String?
|
||||
checkoutSessionId String?
|
||||
subscriptionTier String?
|
||||
subscriptionPlan String?
|
||||
subscriptionStatus String?
|
||||
sendEmail Boolean @default(false)
|
||||
datePaid DateTime?
|
||||
credits Int @default(3)
|
||||
relatedObject RelatedObject[]
|
||||
externalAuthAssociations SocialLogin[]
|
||||
contactFormMessages ContactFormMessage[]
|
||||
gptResponses GptResponse[]
|
||||
contactFormMessages ContactFormMessage[]
|
||||
tasks Task[]
|
||||
files File[]
|
||||
psl=}
|
||||
```
|
||||
|
||||
@@ -46,7 +47,7 @@ entity User {=psl
|
||||
//...
|
||||
stripeId String?
|
||||
checkoutSessionId String?
|
||||
subscriptionTier String?
|
||||
subscriptionPlan String?
|
||||
subscriptionStatus String?
|
||||
datePaid DateTime?
|
||||
credits Int @default(3)
|
||||
@@ -56,17 +57,17 @@ psl=}
|
||||
|
||||
- `stripeId`: The Stripe customer ID. This is created by Stripe on checkout and used to identify the customer.
|
||||
- `checkoutSessionId`: The Stripe checkout session ID. This is created by Stripe on checkout and used to identify the checkout session.
|
||||
- `subscriptionTier`: The subscription tier the user is on. This is set by the app and is used to determine what features the user has access to. By default, we have two tiers: `hobby-tier` and `pro-tier`.
|
||||
- `subscriptionPlan`: The subscription plan the user is on. This is set by the app and is used to determine what features the user has access to. By default, we have two plan: `hobby` and `pro`.
|
||||
- `subscriptionStatus`: The subscription status of the user. This is set by Stripe and is used to determine whether the user has access to the app or not. By default, we have four statuses: `active`, `past_due`, `canceled`, and `deleted`.
|
||||
- `credits` (optional): By default, a user is given 3 credits to trial your product before they have to pay. You can create a one-time purchase product in Stripe to allow users to purchase more credits if they run out.
|
||||
|
||||
### Subscription Statuses
|
||||
|
||||
In general, we determine if a user has paid for an initial subscription by checking if the `subscriptionStatus` field is set. This field is set by Stripe within your webhook handler and is used to signify more detailed information on the user's current status. By default, the template handles four statuses: `active`, `past_due`, `canceled`, and `deleted`.
|
||||
In general, we determine if a user has paid for an initial subscription by checking if the `subscriptionStatus` field is set. This field is set by Stripe within your webhook handler and is used to signify more detailed information on the user's current status. By default, the template handles four statuses: `active`, `past_due`, `canceled_at_period_end`, and `deleted`.
|
||||
|
||||
- When `active` the user has paid for a subscription and has full access to the app.
|
||||
|
||||
- When `canceled`, the user has canceled their subscription and has access to the app until the end of their billing period.
|
||||
- When `canceled_at_period_end`, the user has canceled their subscription and has access to the app until the end of their billing period.
|
||||
|
||||
- When `deleted`, the user has reached the end of their subscription period after canceling and no longer has access to the app.
|
||||
|
||||
@@ -98,13 +99,13 @@ if (subscription.status === 'past_due') {
|
||||
|
||||
See the client-side [authorization section](/guides/authorization) below for more info on how to handle these statuses within your app.
|
||||
|
||||
### Subscription Tiers
|
||||
### Subscription Plans
|
||||
|
||||
The `subscriptionTier` field is used to determine what features the user has access to.
|
||||
The `subscriptionPlan` field is used to determine what features the user has access to.
|
||||
|
||||
By default, we have two tiers: `hobby-tier` and `pro-tier`.
|
||||
By default, we have two plans: `hobby` and `pro`.
|
||||
|
||||
You can add more tiers by adding more products and price IDs to your Stripe product and updating environment variables in your `.env.server` file as well as the relevant code in your app.
|
||||
You can add more plans by adding more products and price IDs to your Stripe product and updating environment variables in your `.env.server` file as well as the relevant code in your app.
|
||||
|
||||
See the [Stripe Integration Guide](/guides/stripe-integration) for more info on how to do this.
|
||||
|
||||
|
||||
@@ -111,8 +111,48 @@ After deploying your server, you need to add the correct redirect URIs to the cr
|
||||
|
||||
### Setting up your Stripe Webhook
|
||||
|
||||
Now you need to set up your stripe webhook for production use.
|
||||
Now you need to set up your stripe webhook for production use. Below are some important steps and considerations you should take as you prepare to deploy your app to production.
|
||||
|
||||
#### Stripe API Versions
|
||||
|
||||
When you create your Stripe account, Stripe will automatically assign you to their latest API version at that time. This API version is important because it determines the structure of the responses Stripe sends to your webhook, as well as the structure it expects of the requests you make toward the Stripe API.
|
||||
|
||||
Because this template was built with a specific version of the Stripe API in mind, it could be that your Stripe account is set to a different API version.
|
||||
|
||||
:::note
|
||||
```ts title="stripeClient.ts"
|
||||
export const stripe = new Stripe(process.env.STRIPE_KEY!, {
|
||||
apiVersion: 'YYYY-MM-DD', // e.g. 2023-08-16
|
||||
});
|
||||
```
|
||||
When you specify a specific API version in your Stripe client, the requests you send to Stripe from your server, along with their responses, will match that API version. On the other hand, Stripe will send all other events to your webhook that didn't originate as a request sent from your server, like those made after a user completes a payment on checkout, using the default API version of the API.
|
||||
|
||||
This is why it's important to make sure your Stripe client version also matches the API version in your Stripe account, and to thoroughly test any changes you make to your Stripe client before deploying to production.
|
||||
:::
|
||||
|
||||
To make sure your app is consistent with your Stripe account, here are some steps you can follow:
|
||||
|
||||
1. You can find your `default` API version in the Stripe dashboard under the [Developers](https://dashboard.stripe.com/developers) section.
|
||||
2. Check that the API version in your `stripe/stripeClient.ts` file matches the default API version in your dashboard:
|
||||
```ts title="stripeClient.ts" {2}
|
||||
export const stripe = new Stripe(process.env.STRIPE_KEY!, {
|
||||
apiVersion: 'YYYY-MM-DD', // e.g. 2023-08-16
|
||||
});
|
||||
```
|
||||
3. If they don't match, you can upgrade/downgrade your Stripe NPM package in `package.json` to match the API version in your dashboard:
|
||||
- If your default version on the Stripe dashboard is also the latest version of the API, you can simply upgrade your Stripe NPM package to the latest version.
|
||||
- If your default version on the Stripe dashboard is not the latest version, and you don't want to [upgrade to the latest version](https://docs.stripe.com/upgrades#how-can-i-upgrade-my-api), because e.g. you have other projects that depend on the current version, you can find and install the Stripe NPM package version that matches your default API version by following these steps:
|
||||
- Find and note the date of your default API version in the [developer dashboard](https://dashboard.stripe.com/developers).
|
||||
- Go to the [Stripe NPM package](https://www.npmjs.com/package/stripe) page and hover over `Published` date column until you find the package release that matches your version. For example, here we find the NPM version that matches the default API version of `2023-08-16` in our dashboard, which is `13.x.x`.
|
||||

|
||||
- Install the correct version of the Stripe NPM package by running, :
|
||||
```sh
|
||||
npm install stripe@x.x.x # e.g. npm install stripe@13.11.0
|
||||
```
|
||||
4. **Test your app thoroughly** to make sure that the changes you made to your Stripe client are working as expected before deploying to production.
|
||||
|
||||
|
||||
#### Creating Your Production Webhook
|
||||
1. go to [https://dashboard.stripe.com/webhooks](https://dashboard.stripe.com/webhooks)
|
||||
2. click on `+ add endpoint`
|
||||
3. enter your endpoint url, which will be the url of your deployed server + `/stripe-webhook`, e.g. `https://open-saas-wasp-sh-server.fly.dev/stripe-webhook`
|
||||
|
||||
@@ -203,25 +203,25 @@ The logic for creating the Stripe Checkout session is defined in the `src/server
|
||||
|
||||
a) define the action in the `main.wasp` file
|
||||
```js title="main.wasp"
|
||||
action stripePayment {
|
||||
fn: import { stripePayment } from "@src/server/actions.js",
|
||||
action generateStripeCheckoutSession {
|
||||
fn: import { generateStripeCheckoutSession } from "@src/server/actions.js",
|
||||
entities: [User]
|
||||
}
|
||||
```
|
||||
|
||||
b) implement the action in the `src/server/actions.ts` file
|
||||
```js title="src/server/actions.ts"
|
||||
export const stripePayment = async (tier, context) => {
|
||||
export const generateStripeCheckoutSession = async (paymentPlanId, context) => {
|
||||
//...
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
c) call the action on the client-side
|
||||
```js title="src/client/app/SubscriptionPage.tsx"
|
||||
import { stripePayment } from "wasp/client/operations";
|
||||
import { generateStripeCheckoutSession } from "wasp/client/operations";
|
||||
|
||||
const handleBuyClick = async (tierId) => {
|
||||
const stripeResults = await stripePayment(tierId);
|
||||
const handleBuyClick = async (paymentPlanId) => {
|
||||
const stripeCheckoutSession = await generateStripeCheckoutSession(paymentPlanId);
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user