Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor Stripe webhook #200

Merged
merged 30 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7c06409
rename TierIds to PaymentPlanIds
vincanger Jun 28, 2024
5b736ab
refactor webhook and util functions
vincanger Jul 1, 2024
41f8884
pass userDelegate to function
vincanger Jul 2, 2024
cf079ac
Merge branch 'main' into refactor-subscription-logic
vincanger Jul 2, 2024
2ed6d6b
Update dbSeeds.ts
vincanger Jul 2, 2024
1f9c766
Merge branch 'main' into refactor-subscription-logic
vincanger Jul 2, 2024
f8c65b3
update app diff
vincanger Jul 2, 2024
b63924b
Update template/app/src/server/stripe/stripeClient.ts
vincanger Jul 3, 2024
eed60a3
extract event handlers and more
vincanger Jul 3, 2024
3c17bdb
Merge branch 'refactor-subscription-logic' of https://github.com/wasp…
vincanger Jul 3, 2024
4a5a69a
Update AccountPage.tsx
vincanger Jul 4, 2024
77f0517
address filips pro effective typescripting and stuff
vincanger Jul 4, 2024
373cb5e
Martin's attempt at consolidating types.
Martinsos Jul 4, 2024
de108da
fix
Martinsos Jul 4, 2024
b013f21
fix webhook events and validation
vincanger Jul 5, 2024
1b8dae1
small changes
vincanger Jul 5, 2024
3d40416
put stripe event handlers back for marty merge
vincanger Jul 6, 2024
cbc9d66
Merge branch 'refactor-subscription-logic-martin-attempt' into refact…
vincanger Jul 6, 2024
73089dc
merge consilidated types from martin
vincanger Jul 6, 2024
744c7db
move some types around
vincanger Jul 6, 2024
62e918b
add docs for stripe api version
vincanger Jul 6, 2024
71242c0
Update AccountPage.tsx
vincanger Jul 8, 2024
d88ad0f
Update stripe.ts
vincanger Jul 8, 2024
d89a35f
update SubscriptionStatus type
vincanger Jul 8, 2024
d62d186
Update actions.ts
vincanger Jul 8, 2024
6829173
add assertUnreachable util
vincanger Jul 8, 2024
4af43d4
more small changes
vincanger Jul 9, 2024
d5ae7c4
Update deploying.md
vincanger Jul 9, 2024
3f9836f
update accountPage and docs
vincanger Jul 10, 2024
b2174db
update app_diff
vincanger Jul 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion opensaas-sh/app_diff/main.wasp.diff
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,5 @@
+ // the admin dashboard but won't be able to see the other users' data, only mock user data.
+ isMockUser Boolean @default(false)

stripeId String?
stripeId String? @unique
checkoutSessionId String?
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
@@ -11,6 +11,7 @@
const [email, setEmail] = useState<string | undefined>(undefined);
const [isAdminFilter, setIsAdminFilter] = useState<boolean | undefined>(undefined);
const [statusOptions, setStatusOptions] = useState<SubscriptionStatusOptions[]>([]);
const [statusOptions, setStatusOptions] = useState<SubscriptionStatus[]>([]);
+ const [isDemoInfoVisible, setIsDemoInfoVisible] = useState(false);
const { data, isLoading, error } = useQuery(getPaginatedUsers, {
skip,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
+ testimonials,
+} from './contentSections';
import DropdownUser from '../components/DropdownUser';
-import { DOCS_URL } from '../../shared/constants';
+import { DOCS_URL, GITHUB_URL } from '../../shared/constants';
-import { DocsUrl } from '../../common';
+import { DocsUrl, GithubUrl } from '../../common';
import { UserMenuItems } from '../components/UserMenuItems';
import DarkModeSwitcher from '../admin/components/DarkModeSwitcher';

Expand Down Expand Up @@ -137,14 +137,14 @@
</p>
<div className='mt-10 flex items-center justify-center gap-x-6'>
<a
href={DOCS_URL}
href={DocsUrl}
- className='rounded-md px-3.5 py-2.5 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-200 hover:ring-2 hover:ring-yellow-300 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:text-white'
+ className='rounded-md px-6 py-4 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-200 hover:ring-2 hover:ring-yellow-300 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:text-white'
>
Get Started <span aria-hidden='true'>→</span>
</a>
+ <a
+ href={GITHUB_URL}
+ href={GithubUrl}
+ className='group relative flex items-center justify-center rounded-md bg-gray-100 px-6 py-4 text-sm font-semibold shadow-sm ring-1 ring-inset ring-gray-200 dark:bg-gray-700 hover:ring-2 hover:ring-yellow-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600'
+ >
+ {/* <AiFillGithub size='1.25rem' className='mr-2' /> */}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
--- template/app/src/client/landing-page/contentSections.ts
+++ opensaas-sh/app/src/client/landing-page/contentSections.ts
@@ -1,74 +1,150 @@
-import { DOCS_URL, BLOG_URL } from '../../shared/constants';
@@ -1,74 +1,126 @@
-import { DocsUrl, BlogUrl } from '../../common';
-import daBoiAvatar from '../static/da-boi.png';
-import avatarPlaceholder from '../static/avatar-placeholder.png';
import { routes } from 'wasp/client/router';
+import { DOCS_URL, BLOG_URL, GITHUB_URL } from '../../shared/constants';
+import daBoiAavatar from '../static/da-boi.png';
-import { routes } from 'wasp/client/router';
+import { DocsUrl, BlogUrl, GithubUrl } from '../../common';

export const navigation = [
{ name: 'Features', href: '#features' },
- { name: 'Pricing', href: routes.PricingPageRoute.build() },
{ name: 'Documentation', href: DOCS_URL },
{ name: 'Blog', href: BLOG_URL },
{ name: 'Documentation', href: DocsUrl },
{ name: 'Blog', href: BlogUrl },
];
export const features = [
{
Expand All @@ -22,16 +21,16 @@
+ description:
+ 'The repo and framework are 100% open-source, and so are the services wherever possible. Still missing something? Contribute!',
icon: '🤝',
href: DOCS_URL,
href: DocsUrl,
},
{
- name: 'Cool Feature #2',
- description: 'Describe your cool feature here.',
+ name: 'DIY Auth, Done For You',
+ description: 'Pre-configured full-stack Auth that you own. No 3rd-party services or hidden fees.',
icon: '🔐',
- href: DOCS_URL,
+ href: DOCS_URL + '/guides/authentication/',
- href: DocsUrl,
+ href: DocsUrl + '/guides/authentication/',
},
{
- name: 'Cool Feature #3',
Expand All @@ -40,7 +39,7 @@
+ description:
+ 'Full support for TypeScript with auto-generated types that span the whole stack. Nothing to configure!',
icon: '🥞',
href: DOCS_URL,
href: DocsUrl,
},
{
- name: 'Cool Feature #4',
Expand All @@ -49,56 +48,56 @@
+ description:
+ "No SaaS is complete without payments. That's why payments and the necessary webhooks are built-in.",
icon: '💸',
+ href: DOCS_URL + '/guides/stripe-integration/',
+ href: DocsUrl + '/guides/stripe-integration/',
+ },
+ {
+ name: 'Admin Dashboard',
+ description: 'Graphs! Tables! Analytics w/ Plausible or Google! All in one place. Ooooooooooh.',
+ icon: '📈',
+ href: DOCS_URL + '/general/admin-dashboard/',
+ href: DocsUrl + '/general/admin-dashboard/',
+ },
+ {
+ name: 'Email Sending',
+ description:
+ 'Email sending built-in. Combine it with the cron jobs feature to easily send emails to your customers.',
+ icon: '📧',
+ href: DOCS_URL + '/guides/email-sending/',
+ href: DocsUrl + '/guides/email-sending/',
+ },
+ {
+ name: 'OpenAI API Implemented',
+ description: 'Have a sweet AI-powered app concept? Get your idea shipped to potential customers in days!',
+ icon: '🤖',
+ href: DOCS_URL,
+ href: DocsUrl,
+ },
+ {
+ name: 'File Uploads with AWS',
+ description: 'File upload examples with AWS S3 presigned URLs are included and fully documented!',
+ icon: '📁',
+ href: DOCS_URL + '/guides/file-uploading/',
+ href: DocsUrl + '/guides/file-uploading/',
+ },
+ {
+ name: 'Deploy Anywhere. Easily.',
+ description:
+ 'No vendor lock-in because you own all your code. Deploy yourself, or let Wasp deploy it for you with a single command.',
+ icon: '🚀 ',
+ href: DOCS_URL + '/guides/deploying/',
+ href: DocsUrl + '/guides/deploying/',
+ },
+ {
+ name: 'Blog w/ Astro',
+ description:
+ 'Built-in blog with the Astro framework. Write your posts in Markdown, and watch your SEO performance take off.',
+ icon: '📝',
+ href: DOCS_URL + '/start/guided-tour/',
+ href: DocsUrl + '/start/guided-tour/',
+ },
+ {
+ name: 'Complete Documentation & Support',
+ description: "We don't leave you hanging. We have detailed docs and a Discord community to help!",
+ icon: '🫂',
href: DOCS_URL,
href: DocsUrl,
},
];
export const testimonials = [
- {
{
- name: 'Da Boi',
- role: 'Wasp Mascot',
- avatarSrc: daBoiAvatar,
Expand All @@ -118,42 +117,19 @@
- avatarSrc: avatarPlaceholder,
- socialUrl: '#',
- quote: 'My cats love it!',
+ // {
+ // name: 'Jason Warner',
+ // role: 'former CTO @ GitHub',
+ // avatarSrc: 'https://pbs.twimg.com/profile_images/1538765024021258240/qXJBzw6U_400x400.jpg',
+ // socialUrl: 'https://twitter.com/jasoncwarner',
+ // quote:
+ // "I've actually had a bunch of fun with [Wasp]... I loved Batman.js back in the day and getting some of those vibes.",
+ // },
+ {
+ name: 'Max Khamrovskyi',
+ role: 'Senior Eng @ Red Hat',
+ avatarSrc: 'https://pbs.twimg.com/profile_images/1719397191205179392/V_QrGPSO_400x400.jpg',
+ socialUrl: 'https://twitter.com/maksim36ua',
+ quote: 'I used Wasp to build and sell my AI-augmented SaaS app for marketplace vendors within two months!',
+ },
+ // {
+ // name: 'Da Boi',
+ // role: 'Wasp Mascot',
+ // avatarSrc: daBoiAavatar,
+ // socialUrl: 'https://twitter.com/wasplang',
+ // quote: "I don't even know how to code. I'm just a plushie.",
+ // },
+ {
+ name: 'Tim Skaggs',
+ role: 'Founder @ Antler US',
+ avatarSrc: 'https://pbs.twimg.com/profile_images/1486119226771480577/VptdEo6A_400x400.png',
+ socialUrl: 'https://twitter.com/tskaggs',
+ quote: 'Nearly done with a MVP in 3 days of part-time work... and deployed on Fly.io in 10 minutes.',
+ },
+ // {
+ // name: 'Fecony',
+ // role: 'Wasp Expert',
+ // avatarSrc: 'https://pbs.twimg.com/profile_images/1560677466749943810/QIFuQMqU_400x400.jpg',
+ // socialUrl: 'https://twitter.com/webrickony',
+ // quote: 'My cats love it!',
+ // },
+ {
+ name: 'Jonathan Cocharan',
+ role: 'Entrepreneur',
Expand Down Expand Up @@ -183,7 +159,7 @@
];
export const footerNavigation = {
app: [
+ { name: 'Github', href: GITHUB_URL },
{ name: 'Documentation', href: DOCS_URL },
{ name: 'Blog', href: BLOG_URL },
+ { name: 'Github', href: GithubUrl },
{ name: 'Documentation', href: DocsUrl },
{ name: 'Blog', href: BlogUrl },
],
6 changes: 6 additions & 0 deletions opensaas-sh/app_diff/src/common.ts.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
--- template/app/src/common.ts
+++ opensaas-sh/app/src/common.ts
@@ -1,2 +1,3 @@
export const DocsUrl = 'https://docs.opensaas.sh';
export const BlogUrl = 'https://docs.opensaas.sh/blog';
+export const GithubUrl = 'https://github.com/wasp-lang/open-saas';
6 changes: 4 additions & 2 deletions opensaas-sh/app_diff/src/server/scripts/dbSeeds.ts.diff
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
--- template/app/src/server/scripts/dbSeeds.ts
+++ opensaas-sh/app/src/server/scripts/dbSeeds.ts
@@ -43,5 +43,6 @@
@@ -42,5 +42,8 @@
datePaid: hasUserPaidOnStripe ? faker.date.between({ from: createdAt, to: lastActiveTimestamp }) : null,
checkoutSessionId: hasUserPaidOnStripe ? `cs_test_${faker.string.uuid()}` : null,
subscriptionTier: subscriptionStatus ? faker.helpers.arrayElement([TierIds.HOBBY, TierIds.PRO]) : null,
subscriptionPlan: subscriptionStatus ? faker.helpers.arrayElement(getSubscriptionPaymentPlanIds()) : null,
+ // For the demo app, we want to default isMockUser to true so that our admin dash only shows mock users
+ // and not real users signing up to test the app
+ isMockUser: true,
};
}
7 changes: 0 additions & 7 deletions opensaas-sh/app_diff/src/shared/constants.ts.diff

This file was deleted.

Binary file added opensaas-sh/blog/public/stripe/npm-version.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 14 additions & 13 deletions opensaas-sh/blog/src/content/docs/general/user-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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=}
```

Expand All @@ -46,7 +47,7 @@ entity User {=psl
//...
stripeId String?
checkoutSessionId String?
subscriptionTier String?
subscriptionPlan String?
subscriptionStatus String?
datePaid DateTime?
credits Int @default(3)
Expand All @@ -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.

Expand Down Expand Up @@ -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.

Expand Down
42 changes: 41 additions & 1 deletion opensaas-sh/blog/src/content/docs/guides/deploying.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Martinsos marked this conversation as resolved.
Show resolved Hide resolved
- 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`.
![stripe-npm-versions](/stripe/npm-version.png)
- Install the correct version of the Stripe NPM package by running, :
```sh
npm install [email protected] # e.g. npm install [email protected]
```
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`
Expand Down
Loading