├── .env.development.example ├── .gitignore ├── LICENSE ├── README.md ├── components ├── Layout.tsx ├── Pricing.tsx ├── icons │ ├── GitHub.tsx │ └── Logo.tsx └── ui │ ├── Button │ ├── Button.module.css │ ├── Button.tsx │ └── index.ts │ ├── Footer │ ├── Footer.tsx │ └── index.ts │ ├── Input │ ├── Input.module.css │ ├── Input.tsx │ └── index.ts │ ├── LoadingDots │ ├── LoadingDots.module.css │ ├── LoadingDots.tsx │ └── index.ts │ └── Navbar │ ├── Navbar.module.css │ ├── Navbar.tsx │ └── index.ts ├── functions ├── _utils │ ├── __generated__ │ │ └── graphql-request.ts │ ├── graphql-client.ts │ ├── graphql │ │ ├── plans.graphql │ │ ├── profiles.graphql │ │ └── users.graphql │ ├── helpers.ts │ └── stripe.ts ├── custom │ └── create-checkout-session.ts ├── events │ └── users │ │ └── insert │ │ └── stripe.ts ├── graphql │ └── stripe.ts ├── test.js └── webhooks │ └── stripe.ts ├── graphql.config.yaml ├── graphql ├── plans.graphql ├── stripe.graphql └── user.graphql ├── next-env.d.ts ├── nhost ├── config.yaml ├── emails │ ├── en │ │ ├── email-confirm-change │ │ │ ├── body.html │ │ │ └── subject.txt │ │ ├── email-verify │ │ │ ├── body.html │ │ │ └── subject.txt │ │ ├── password-reset │ │ │ ├── body.html │ │ │ └── subject.txt │ │ ├── signin-passwordless-sms │ │ │ └── body.txt │ │ └── signin-passwordless │ │ │ ├── body.html │ │ │ └── subject.txt │ └── fr │ │ ├── email-confirm-change │ │ ├── body.html │ │ └── subject.txt │ │ ├── email-verify │ │ ├── body.html │ │ └── subject.txt │ │ ├── password-reset │ │ ├── body.html │ │ └── subject.txt │ │ ├── signin-passwordless-sms │ │ └── body.txt │ │ └── signin-passwordless │ │ ├── body.html │ │ └── subject.txt ├── metadata │ ├── actions.graphql │ ├── actions.yaml │ ├── allow_list.yaml │ ├── api_limits.yaml │ ├── cron_triggers.yaml │ ├── databases │ │ ├── databases.yaml │ │ └── default │ │ │ └── tables │ │ │ ├── auth_provider_requests.yaml │ │ │ ├── auth_providers.yaml │ │ │ ├── auth_refresh_tokens.yaml │ │ │ ├── auth_roles.yaml │ │ │ ├── auth_user_providers.yaml │ │ │ ├── auth_user_roles.yaml │ │ │ ├── auth_user_security_keys.yaml │ │ │ ├── auth_users.yaml │ │ │ ├── public_customers.yaml │ │ │ ├── public_plans.yaml │ │ │ ├── public_products.yaml │ │ │ ├── public_profile.yaml │ │ │ ├── public_profiles.yaml │ │ │ ├── storage_buckets.yaml │ │ │ ├── storage_files.yaml │ │ │ └── tables.yaml │ ├── graphql_schema_introspection.yaml │ ├── inherited_roles.yaml │ ├── network.yaml │ ├── query_collections.yaml │ ├── remote_schemas.yaml │ ├── rest_endpoints.yaml │ └── version.yaml ├── migrations │ └── default │ │ ├── 1669308895353_create_table_public_profile │ │ ├── down.sql │ │ └── up.sql │ │ ├── 1669316801508_create_table_public_products │ │ ├── down.sql │ │ └── up.sql │ │ ├── 1669317002587_alter_table_public_products_alter_column_price_id │ │ ├── down.sql │ │ └── up.sql │ │ ├── 1669325785961_run_sql_migration │ │ ├── down.sql │ │ └── up.sql │ │ ├── 1669975961205_alter_table_public_profiles_add_column_plan_id │ │ ├── down.sql │ │ └── up.sql │ │ ├── 1669975970500_alter_table_public_products_alter_column_price_id │ │ ├── down.sql │ │ └── up.sql │ │ └── 1669976516108_rename_table_public_products │ │ ├── down.sql │ │ └── up.sql └── seeds │ └── default │ └── 001-plans.sql ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── account.tsx ├── index.tsx └── signin.tsx ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── demo.png ├── favicon.ico ├── github.svg ├── nextjs.svg ├── nhost.svg ├── og.png ├── stripe.svg └── vercel.svg ├── styles ├── chrome-bug.css └── main.css ├── tailwind.config.js ├── tsconfig.json ├── types.ts └── utils ├── __generated__ └── graphql.ts ├── graphql-fetcher.ts ├── helpers.ts ├── nhost.ts └── react-query-client.ts /.env.development.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_NHOST_SUBDOMAIN=localhost 2 | NEXT_PUBLIC_NHOST_REGION= 3 | 4 | # Stripe 5 | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_123 6 | STRIPE_SECRET_KEY=sk_test_123 7 | STRIPE_WEBHOOK_SECRET=whsec_123 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # editors 37 | .vscode 38 | 39 | .nhost 40 | .env.development 41 | functions/node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Vercel, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js Stripe SaaS Starter 2 | 3 | The ultimate starter kit for high-performance SaaS applications. 4 | 5 | ## Features 6 | 7 | - Secure user management and authentication with [Nhost](https://docs.nhost.io/authentication). 8 | - Powerful data access & management tooling on top of PostgreSQL with [Nhost](https://docs.nhost.io/database). 9 | - Integration with [Stripe Checkout](https://stripe.com/docs/payments/checkout) and the [Stripe customer portal](https://stripe.com/docs/billing/subscriptions/customer-portal). 10 | - Automatic syncing of pricing plans and subscription statuses via [Stripe webhooks](https://stripe.com/docs/webhooks). 11 | 12 | ## Features 13 | 14 | - Postgres Database 15 | - GraphQL API 16 | - Magic Link and GitHub Authentication 17 | - Email Templates 18 | - Remote Stripe GraphQL API 19 | - Next.js 20 | - TypeScript 21 | - Tailwind CSS 22 | - GraphQL Codegen with React Query 23 | 24 | ## Demo 25 | 26 | - [https://nextjs-stripe-starter-template.vercel.app/](https://nextjs-stripe-starter-template.vercel.app/) 27 | 28 | [](https://xxx.vercel.app/) 29 | 30 | ## Development Setup 31 | 32 | ### Nhost 33 | 34 | - Clone this repo. 35 | - Copy `.env.local.example` to `.env.development`. 36 | - Add the following environment variables from [Stripe](https://dashboard.stripe.com/test/apikeys) to the `.env.development` file: 37 | - `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` 38 | - `STRIPE_SECRET_KEY` 39 | 40 | ### Stripe (test mode) 41 | 42 | - Create 4 products in the [Stripe Dashboard](https://stripe.com/docs/products-prices/getting-started). 43 | - Add the products to the `nhost/seeds/default/001-plans.sql` file. The first time you're starting the Nhost project using `nhost up` the seed script will run and add the data to your `plans` table. 44 | 45 | ### Stripe Webhooks (test mode) 46 | 47 | - Make sure you have the [Stripe CLI](https://stripe.com/docs/stripe-cli) installed. 48 | - Run `pnpm stripe:listen`. 49 | - You'll see an output stating the Webhook signing secret starting with `whsec_`. 50 | - Copy the Webhook signing secret to your `.env.development` for `STRIPE_WEBHOOK_SECRET`. 51 | 52 | ### Frontend and Backend 53 | 54 | - Start the backend with `nhost up`. 55 | - Start the frontend with `pnpm dev`. 56 | 57 | You now have a fully working backend and frontend with Next.js, Nhost, and Stripe. 58 | 59 | ## Go Live 60 | 61 | ### Nhost 62 | 63 | - Add the following [environment variables](https://docs.nhost.io/platform/environment-variables) from [Stripe](https://stripe.com/docs/keys#test-live-modes) (using live mode): 64 | - `STRIPE_SECRET_KEY` 65 | - `STRIPE_WEBHOOK_SECRET` 66 | - Connect your Nhost project to your [Git repository](https://docs.nhost.io/platform/git). 67 | - This will create the tables automatically for you. 68 | 69 | ### Stripe (live mode) 70 | 71 | - Create 4 products in the [Stripe Dashboard](https://stripe.com/docs/products-prices/getting-started) (live mode). 72 | - Add the products to the `plans` Plans database in the Nhost Dashboard. 73 | 74 | ### Stripe Webhooks (live mode) 75 | 76 | - Add a [webhook endpoint in Stripe](https://dashboard.stripe.com/webhooks) pointing to `https://{subdomain}.functions.{region}.nhost.run/v1/webhook/stripe`. 77 | - Configure the events you want to listen to. It's OK to listen to all events. 78 | - Copy the "Signing secret" and add it as an [environment variable](https://docs.nhost.io/platform/environment-variables) in Nhost with the name `STRIPE_WEBHOOK_SECRET`. 79 | 80 | ### Frontend Hosting 81 | 82 | You can use any frontend hosting, like [Vercel](https://vercel.com/) and [Netlify](https://netlify.com/). The process is the same: 83 | 84 | - Connect the repo with a new project in the frontend hosting service. 85 | - Add the following: 86 | - `NEXT_PUBLIC_NHOST_SUBDOMAIN` (from Nhost) 87 | - `NEXT_PUBLIC_NHOST_REGION` (from Nhost) 88 | - `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` (from Stripe) 89 | - Retrigger a deployment so the newly added environment variables are applied. 90 | -------------------------------------------------------------------------------- /components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import { useRouter } from 'next/router'; 3 | 4 | import Navbar from 'components/ui/Navbar'; 5 | import Footer from 'components/ui/Footer'; 6 | import { ReactNode } from 'react'; 7 | import { PageMeta } from '../types'; 8 | 9 | interface Props { 10 | children: ReactNode; 11 | meta?: PageMeta; 12 | } 13 | 14 | export default function Layout({ children, meta: pageMeta }: Props) { 15 | const router = useRouter(); 16 | const meta = { 17 | title: 'Next.js Subscription Starter', 18 | description: 'Brought to you by Next.js, Stripe, and Nhost.', 19 | cardImage: '/og.png', 20 | ...pageMeta 21 | }; 22 | 23 | return ( 24 | <> 25 |
26 |91 | No subscription pricing plans found. 92 |
93 |110 | Start building for free, then add a site plan to go live. Account 111 | plans unlock additional features. 112 |
113 |{plan.description}
146 |147 | 148 | {priceString} 149 | 150 | 151 | /mo 152 | 153 |
154 | 166 |173 | Brought to you by 174 |
175 | 202 |Use this link to confirm changing email:
11 |12 | 13 | Change email 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /nhost/emails/en/email-confirm-change/subject.txt: -------------------------------------------------------------------------------- 1 | Change your email address -------------------------------------------------------------------------------- /nhost/emails/en/email-verify/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Use this link to verify your email:
11 |12 | 13 | Verify Email 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /nhost/emails/en/email-verify/subject.txt: -------------------------------------------------------------------------------- 1 | Verify your email -------------------------------------------------------------------------------- /nhost/emails/en/password-reset/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Use this link to reset your password:
11 |12 | 13 | Reset password 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /nhost/emails/en/password-reset/subject.txt: -------------------------------------------------------------------------------- 1 | Reset your password -------------------------------------------------------------------------------- /nhost/emails/en/signin-passwordless-sms/body.txt: -------------------------------------------------------------------------------- 1 | Your code is ${code}. -------------------------------------------------------------------------------- /nhost/emails/en/signin-passwordless/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Use this link to securely sign in:
11 |12 | 13 | Sign In 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /nhost/emails/en/signin-passwordless/subject.txt: -------------------------------------------------------------------------------- 1 | Secure sign-in link -------------------------------------------------------------------------------- /nhost/emails/fr/email-confirm-change/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Utilisez ce lien pour confirmer le changement de courriel:
11 |12 | 13 | Changer courriel 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /nhost/emails/fr/email-confirm-change/subject.txt: -------------------------------------------------------------------------------- 1 | Changez votre adresse courriel 2 | -------------------------------------------------------------------------------- /nhost/emails/fr/email-verify/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Utilisez ce lien pour vérifier votre courriel:
11 |12 | 13 | Vérifier courriel 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /nhost/emails/fr/email-verify/subject.txt: -------------------------------------------------------------------------------- 1 | Vérifier votre courriel 2 | -------------------------------------------------------------------------------- /nhost/emails/fr/password-reset/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Utilisez ce lien pour réinitializer votre mot de passe:
11 |12 | 13 | Réinitializer mot de passe 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /nhost/emails/fr/password-reset/subject.txt: -------------------------------------------------------------------------------- 1 | Réinitialiser votre mot de passe 2 | -------------------------------------------------------------------------------- /nhost/emails/fr/signin-passwordless-sms/body.txt: -------------------------------------------------------------------------------- 1 | Votre code est ${code}. -------------------------------------------------------------------------------- /nhost/emails/fr/signin-passwordless/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Utilisez ce lien pour vous connecter de façon sécuritaire:
11 |12 | 13 | Connexion 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /nhost/emails/fr/signin-passwordless/subject.txt: -------------------------------------------------------------------------------- 1 | Lien de connexion sécurisé 2 | -------------------------------------------------------------------------------- /nhost/metadata/actions.graphql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhost/nextjs-stripe-starter/686d3d75e1ea13f755f78ae47c8faa1ac8be27fd/nhost/metadata/actions.graphql -------------------------------------------------------------------------------- /nhost/metadata/actions.yaml: -------------------------------------------------------------------------------- 1 | actions: [] 2 | custom_types: 3 | enums: [] 4 | input_objects: [] 5 | objects: [] 6 | scalars: [] 7 | -------------------------------------------------------------------------------- /nhost/metadata/allow_list.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /nhost/metadata/api_limits.yaml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /nhost/metadata/cron_triggers.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /nhost/metadata/databases/databases.yaml: -------------------------------------------------------------------------------- 1 | - name: default 2 | kind: postgres 3 | configuration: 4 | connection_info: 5 | database_url: 6 | from_env: HASURA_GRAPHQL_DATABASE_URL 7 | isolation_level: read-committed 8 | pool_settings: 9 | connection_lifetime: 600 10 | idle_timeout: 180 11 | max_connections: 50 12 | retries: 20 13 | use_prepared_statements: true 14 | tables: "!include default/tables/tables.yaml" 15 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/auth_provider_requests.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: provider_requests 3 | schema: auth 4 | configuration: 5 | column_config: 6 | id: 7 | custom_name: id 8 | options: 9 | custom_name: options 10 | custom_column_names: 11 | id: id 12 | options: options 13 | custom_name: authProviderRequests 14 | custom_root_fields: 15 | delete: deleteAuthProviderRequests 16 | delete_by_pk: deleteAuthProviderRequest 17 | insert: insertAuthProviderRequests 18 | insert_one: insertAuthProviderRequest 19 | select: authProviderRequests 20 | select_aggregate: authProviderRequestsAggregate 21 | select_by_pk: authProviderRequest 22 | update: updateAuthProviderRequests 23 | update_by_pk: updateAuthProviderRequest 24 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/auth_providers.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: providers 3 | schema: auth 4 | configuration: 5 | column_config: 6 | id: 7 | custom_name: id 8 | custom_column_names: 9 | id: id 10 | custom_name: authProviders 11 | custom_root_fields: 12 | delete: deleteAuthProviders 13 | delete_by_pk: deleteAuthProvider 14 | insert: insertAuthProviders 15 | insert_one: insertAuthProvider 16 | select: authProviders 17 | select_aggregate: authProvidersAggregate 18 | select_by_pk: authProvider 19 | update: updateAuthProviders 20 | update_by_pk: updateAuthProvider 21 | array_relationships: 22 | - name: userProviders 23 | using: 24 | foreign_key_constraint_on: 25 | column: provider_id 26 | table: 27 | name: user_providers 28 | schema: auth 29 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/auth_refresh_tokens.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: refresh_tokens 3 | schema: auth 4 | configuration: 5 | column_config: 6 | created_at: 7 | custom_name: createdAt 8 | expires_at: 9 | custom_name: expiresAt 10 | refresh_token: 11 | custom_name: refreshToken 12 | user_id: 13 | custom_name: userId 14 | custom_column_names: 15 | created_at: createdAt 16 | expires_at: expiresAt 17 | refresh_token: refreshToken 18 | user_id: userId 19 | custom_name: authRefreshTokens 20 | custom_root_fields: 21 | delete: deleteAuthRefreshTokens 22 | delete_by_pk: deleteAuthRefreshToken 23 | insert: insertAuthRefreshTokens 24 | insert_one: insertAuthRefreshToken 25 | select: authRefreshTokens 26 | select_aggregate: authRefreshTokensAggregate 27 | select_by_pk: authRefreshToken 28 | update: updateAuthRefreshTokens 29 | update_by_pk: updateAuthRefreshToken 30 | object_relationships: 31 | - name: user 32 | using: 33 | foreign_key_constraint_on: user_id 34 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/auth_roles.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: roles 3 | schema: auth 4 | configuration: 5 | column_config: 6 | role: 7 | custom_name: role 8 | custom_column_names: 9 | role: role 10 | custom_name: authRoles 11 | custom_root_fields: 12 | delete: deleteAuthRoles 13 | delete_by_pk: deleteAuthRole 14 | insert: insertAuthRoles 15 | insert_one: insertAuthRole 16 | select: authRoles 17 | select_aggregate: authRolesAggregate 18 | select_by_pk: authRole 19 | update: updateAuthRoles 20 | update_by_pk: updateAuthRole 21 | array_relationships: 22 | - name: userRoles 23 | using: 24 | foreign_key_constraint_on: 25 | column: role 26 | table: 27 | name: user_roles 28 | schema: auth 29 | - name: usersByDefaultRole 30 | using: 31 | foreign_key_constraint_on: 32 | column: default_role 33 | table: 34 | name: users 35 | schema: auth 36 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/auth_user_providers.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: user_providers 3 | schema: auth 4 | configuration: 5 | column_config: 6 | access_token: 7 | custom_name: accessToken 8 | created_at: 9 | custom_name: createdAt 10 | id: 11 | custom_name: id 12 | provider_id: 13 | custom_name: providerId 14 | provider_user_id: 15 | custom_name: providerUserId 16 | refresh_token: 17 | custom_name: refreshToken 18 | updated_at: 19 | custom_name: updatedAt 20 | user_id: 21 | custom_name: userId 22 | custom_column_names: 23 | access_token: accessToken 24 | created_at: createdAt 25 | id: id 26 | provider_id: providerId 27 | provider_user_id: providerUserId 28 | refresh_token: refreshToken 29 | updated_at: updatedAt 30 | user_id: userId 31 | custom_name: authUserProviders 32 | custom_root_fields: 33 | delete: deleteAuthUserProviders 34 | delete_by_pk: deleteAuthUserProvider 35 | insert: insertAuthUserProviders 36 | insert_one: insertAuthUserProvider 37 | select: authUserProviders 38 | select_aggregate: authUserProvidersAggregate 39 | select_by_pk: authUserProvider 40 | update: updateAuthUserProviders 41 | update_by_pk: updateAuthUserProvider 42 | object_relationships: 43 | - name: provider 44 | using: 45 | foreign_key_constraint_on: provider_id 46 | - name: user 47 | using: 48 | foreign_key_constraint_on: user_id 49 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/auth_user_roles.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: user_roles 3 | schema: auth 4 | configuration: 5 | column_config: 6 | created_at: 7 | custom_name: createdAt 8 | id: 9 | custom_name: id 10 | role: 11 | custom_name: role 12 | user_id: 13 | custom_name: userId 14 | custom_column_names: 15 | created_at: createdAt 16 | id: id 17 | role: role 18 | user_id: userId 19 | custom_name: authUserRoles 20 | custom_root_fields: 21 | delete: deleteAuthUserRoles 22 | delete_by_pk: deleteAuthUserRole 23 | insert: insertAuthUserRoles 24 | insert_one: insertAuthUserRole 25 | select: authUserRoles 26 | select_aggregate: authUserRolesAggregate 27 | select_by_pk: authUserRole 28 | update: updateAuthUserRoles 29 | update_by_pk: updateAuthUserRole 30 | object_relationships: 31 | - name: roleByRole 32 | using: 33 | foreign_key_constraint_on: role 34 | - name: user 35 | using: 36 | foreign_key_constraint_on: user_id 37 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/auth_user_security_keys.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: user_security_keys 3 | schema: auth 4 | configuration: 5 | column_config: 6 | credential_id: 7 | custom_name: credentialId 8 | credential_public_key: 9 | custom_name: credentialPublicKey 10 | id: 11 | custom_name: id 12 | user_id: 13 | custom_name: userId 14 | custom_column_names: 15 | credential_id: credentialId 16 | credential_public_key: credentialPublicKey 17 | id: id 18 | user_id: userId 19 | custom_name: authUserSecurityKeys 20 | custom_root_fields: 21 | delete: deleteAuthUserSecurityKeys 22 | delete_by_pk: deleteAuthUserSecurityKey 23 | insert: insertAuthUserSecurityKeys 24 | insert_one: insertAuthUserSecurityKey 25 | select: authUserSecurityKeys 26 | select_aggregate: authUserSecurityKeysAggregate 27 | select_by_pk: authUserSecurityKey 28 | update: updateAuthUserSecurityKeys 29 | update_by_pk: updateAuthUserSecurityKey 30 | object_relationships: 31 | - name: user 32 | using: 33 | foreign_key_constraint_on: user_id 34 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/auth_users.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: users 3 | schema: auth 4 | configuration: 5 | column_config: 6 | active_mfa_type: 7 | custom_name: activeMfaType 8 | avatar_url: 9 | custom_name: avatarUrl 10 | created_at: 11 | custom_name: createdAt 12 | default_role: 13 | custom_name: defaultRole 14 | disabled: 15 | custom_name: disabled 16 | display_name: 17 | custom_name: displayName 18 | email: 19 | custom_name: email 20 | email_verified: 21 | custom_name: emailVerified 22 | id: 23 | custom_name: id 24 | is_anonymous: 25 | custom_name: isAnonymous 26 | last_seen: 27 | custom_name: lastSeen 28 | locale: 29 | custom_name: locale 30 | new_email: 31 | custom_name: newEmail 32 | otp_hash: 33 | custom_name: otpHash 34 | otp_hash_expires_at: 35 | custom_name: otpHashExpiresAt 36 | otp_method_last_used: 37 | custom_name: otpMethodLastUsed 38 | password_hash: 39 | custom_name: passwordHash 40 | phone_number: 41 | custom_name: phoneNumber 42 | phone_number_verified: 43 | custom_name: phoneNumberVerified 44 | ticket: 45 | custom_name: ticket 46 | ticket_expires_at: 47 | custom_name: ticketExpiresAt 48 | totp_secret: 49 | custom_name: totpSecret 50 | updated_at: 51 | custom_name: updatedAt 52 | webauthn_current_challenge: 53 | custom_name: currentChallenge 54 | custom_column_names: 55 | active_mfa_type: activeMfaType 56 | avatar_url: avatarUrl 57 | created_at: createdAt 58 | default_role: defaultRole 59 | disabled: disabled 60 | display_name: displayName 61 | email: email 62 | email_verified: emailVerified 63 | id: id 64 | is_anonymous: isAnonymous 65 | last_seen: lastSeen 66 | locale: locale 67 | new_email: newEmail 68 | otp_hash: otpHash 69 | otp_hash_expires_at: otpHashExpiresAt 70 | otp_method_last_used: otpMethodLastUsed 71 | password_hash: passwordHash 72 | phone_number: phoneNumber 73 | phone_number_verified: phoneNumberVerified 74 | ticket: ticket 75 | ticket_expires_at: ticketExpiresAt 76 | totp_secret: totpSecret 77 | updated_at: updatedAt 78 | webauthn_current_challenge: currentChallenge 79 | custom_name: users 80 | custom_root_fields: 81 | delete: deleteUsers 82 | delete_by_pk: deleteUser 83 | insert: insertUsers 84 | insert_one: insertUser 85 | select: users 86 | select_aggregate: usersAggregate 87 | select_by_pk: user 88 | update: updateUsers 89 | update_by_pk: updateUser 90 | object_relationships: 91 | - name: defaultRoleByRole 92 | using: 93 | foreign_key_constraint_on: default_role 94 | - name: profile 95 | using: 96 | foreign_key_constraint_on: 97 | column: id 98 | table: 99 | name: profiles 100 | schema: public 101 | array_relationships: 102 | - name: refreshTokens 103 | using: 104 | foreign_key_constraint_on: 105 | column: user_id 106 | table: 107 | name: refresh_tokens 108 | schema: auth 109 | - name: roles 110 | using: 111 | foreign_key_constraint_on: 112 | column: user_id 113 | table: 114 | name: user_roles 115 | schema: auth 116 | - name: securityKeys 117 | using: 118 | foreign_key_constraint_on: 119 | column: user_id 120 | table: 121 | name: user_security_keys 122 | schema: auth 123 | - name: userProviders 124 | using: 125 | foreign_key_constraint_on: 126 | column: user_id 127 | table: 128 | name: user_providers 129 | schema: auth 130 | select_permissions: 131 | - role: user 132 | permission: 133 | columns: 134 | - display_name 135 | - email 136 | - id 137 | filter: 138 | id: 139 | _eq: X-Hasura-User-Id 140 | event_triggers: 141 | - name: insert-user-stripe 142 | definition: 143 | enable_manual: false 144 | insert: 145 | columns: '*' 146 | retry_conf: 147 | interval_sec: 10 148 | num_retries: 0 149 | timeout_sec: 60 150 | webhook: '{{NHOST_BACKEND_URL}}/v1/functions/events/users/insert/stripe' 151 | headers: 152 | - name: nhost-webhook-secret 153 | value_from_env: NHOST_WEBHOOK_SECRET 154 | cleanup_config: 155 | batch_size: 10000 156 | clean_invocation_logs: false 157 | clear_older_than: 168 158 | paused: true 159 | schedule: 0 0 * * * 160 | timeout: 60 161 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/public_customers.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: customers 3 | schema: public 4 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/public_plans.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: plans 3 | schema: public 4 | configuration: 5 | column_config: 6 | created_at: 7 | custom_name: createdAt 8 | stripe_price_id: 9 | custom_name: stripePriceId 10 | updated_at: 11 | custom_name: updatedAt 12 | custom_column_names: 13 | created_at: createdAt 14 | stripe_price_id: stripePriceId 15 | updated_at: updatedAt 16 | custom_root_fields: {} 17 | array_relationships: 18 | - name: profiles 19 | using: 20 | foreign_key_constraint_on: 21 | column: plan_id 22 | table: 23 | name: profiles 24 | schema: public 25 | select_permissions: 26 | - role: user 27 | permission: 28 | columns: 29 | - amount 30 | - currency 31 | - description 32 | - id 33 | - name 34 | - stripe_price_id 35 | filter: {} 36 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/public_products.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: products 3 | schema: public 4 | configuration: 5 | column_config: 6 | created_at: 7 | custom_name: createdAt 8 | stripe_price_id: 9 | custom_name: stripePriceId 10 | updated_at: 11 | custom_name: updatedAt 12 | custom_column_names: 13 | created_at: createdAt 14 | stripe_price_id: stripePriceId 15 | updated_at: updatedAt 16 | custom_root_fields: {} 17 | array_relationships: 18 | - name: profiles 19 | using: 20 | foreign_key_constraint_on: 21 | column: plan_id 22 | table: 23 | name: profiles 24 | schema: public 25 | select_permissions: 26 | - role: user 27 | permission: 28 | columns: 29 | - amount 30 | - currency 31 | - description 32 | - id 33 | - name 34 | - stripe_price_id 35 | filter: {} 36 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/public_profile.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: profile 3 | schema: public 4 | object_relationships: 5 | - name: user 6 | using: 7 | foreign_key_constraint_on: id 8 | remote_relationships: 9 | - definition: 10 | to_remote_schema: 11 | lhs_fields: 12 | - stripe_customer_id 13 | remote_field: 14 | stripe: 15 | arguments: {} 16 | field: 17 | customer: 18 | arguments: 19 | id: $stripe_customer_id 20 | remote_schema: stripe 21 | name: stripeCustomer 22 | select_permissions: 23 | - role: user 24 | permission: 25 | columns: 26 | - stripe_customer_id 27 | - id 28 | filter: 29 | id: 30 | _eq: X-Hasura-User-Id 31 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/public_profiles.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: profiles 3 | schema: public 4 | configuration: 5 | column_config: 6 | stripe_customer_id: 7 | custom_name: stripeCustomerId 8 | custom_column_names: 9 | stripe_customer_id: stripeCustomerId 10 | custom_root_fields: 11 | insert: insertProfiles 12 | insert_one: insertProfile 13 | object_relationships: 14 | - name: plan 15 | using: 16 | foreign_key_constraint_on: plan_id 17 | - name: user 18 | using: 19 | foreign_key_constraint_on: id 20 | remote_relationships: 21 | - definition: 22 | to_remote_schema: 23 | lhs_fields: 24 | - stripe_customer_id 25 | remote_field: 26 | stripe: 27 | arguments: {} 28 | field: 29 | customer: 30 | arguments: 31 | id: $stripe_customer_id 32 | remote_schema: stripe 33 | name: stripeCustomer 34 | select_permissions: 35 | - role: user 36 | permission: 37 | columns: 38 | - stripe_customer_id 39 | - id 40 | filter: 41 | id: 42 | _eq: X-Hasura-User-Id 43 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/storage_buckets.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: buckets 3 | schema: storage 4 | configuration: 5 | column_config: 6 | cache_control: 7 | custom_name: cacheControl 8 | created_at: 9 | custom_name: createdAt 10 | download_expiration: 11 | custom_name: downloadExpiration 12 | id: 13 | custom_name: id 14 | max_upload_file_size: 15 | custom_name: maxUploadFileSize 16 | min_upload_file_size: 17 | custom_name: minUploadFileSize 18 | presigned_urls_enabled: 19 | custom_name: presignedUrlsEnabled 20 | updated_at: 21 | custom_name: updatedAt 22 | custom_column_names: 23 | cache_control: cacheControl 24 | created_at: createdAt 25 | download_expiration: downloadExpiration 26 | id: id 27 | max_upload_file_size: maxUploadFileSize 28 | min_upload_file_size: minUploadFileSize 29 | presigned_urls_enabled: presignedUrlsEnabled 30 | updated_at: updatedAt 31 | custom_name: buckets 32 | custom_root_fields: 33 | delete: deleteBuckets 34 | delete_by_pk: deleteBucket 35 | insert: insertBuckets 36 | insert_one: insertBucket 37 | select: buckets 38 | select_aggregate: bucketsAggregate 39 | select_by_pk: bucket 40 | update: updateBuckets 41 | update_by_pk: updateBucket 42 | array_relationships: 43 | - name: files 44 | using: 45 | foreign_key_constraint_on: 46 | column: bucket_id 47 | table: 48 | name: files 49 | schema: storage 50 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/storage_files.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: files 3 | schema: storage 4 | configuration: 5 | column_config: 6 | bucket_id: 7 | custom_name: bucketId 8 | created_at: 9 | custom_name: createdAt 10 | etag: 11 | custom_name: etag 12 | id: 13 | custom_name: id 14 | is_uploaded: 15 | custom_name: isUploaded 16 | mime_type: 17 | custom_name: mimeType 18 | name: 19 | custom_name: name 20 | size: 21 | custom_name: size 22 | updated_at: 23 | custom_name: updatedAt 24 | uploaded_by_user_id: 25 | custom_name: uploadedByUserId 26 | custom_column_names: 27 | bucket_id: bucketId 28 | created_at: createdAt 29 | etag: etag 30 | id: id 31 | is_uploaded: isUploaded 32 | mime_type: mimeType 33 | name: name 34 | size: size 35 | updated_at: updatedAt 36 | uploaded_by_user_id: uploadedByUserId 37 | custom_name: files 38 | custom_root_fields: 39 | delete: deleteFiles 40 | delete_by_pk: deleteFile 41 | insert: insertFiles 42 | insert_one: insertFile 43 | select: files 44 | select_aggregate: filesAggregate 45 | select_by_pk: file 46 | update: updateFiles 47 | update_by_pk: updateFile 48 | object_relationships: 49 | - name: bucket 50 | using: 51 | foreign_key_constraint_on: bucket_id 52 | -------------------------------------------------------------------------------- /nhost/metadata/databases/default/tables/tables.yaml: -------------------------------------------------------------------------------- 1 | - "!include auth_provider_requests.yaml" 2 | - "!include auth_providers.yaml" 3 | - "!include auth_refresh_tokens.yaml" 4 | - "!include auth_roles.yaml" 5 | - "!include auth_user_providers.yaml" 6 | - "!include auth_user_roles.yaml" 7 | - "!include auth_user_security_keys.yaml" 8 | - "!include auth_users.yaml" 9 | - "!include public_plans.yaml" 10 | - "!include public_profiles.yaml" 11 | - "!include storage_buckets.yaml" 12 | - "!include storage_files.yaml" 13 | -------------------------------------------------------------------------------- /nhost/metadata/graphql_schema_introspection.yaml: -------------------------------------------------------------------------------- 1 | disabled_for_roles: [] 2 | -------------------------------------------------------------------------------- /nhost/metadata/inherited_roles.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /nhost/metadata/network.yaml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /nhost/metadata/query_collections.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /nhost/metadata/remote_schemas.yaml: -------------------------------------------------------------------------------- 1 | - name: stripe 2 | definition: 3 | url: '{{NHOST_FUNCTIONS_URL}}/graphql/stripe' 4 | timeout_seconds: 60 5 | customization: {} 6 | forward_client_headers: true 7 | comment: "" 8 | permissions: 9 | - role: user 10 | definition: 11 | schema: | 12 | schema { 13 | query: Query 14 | mutation: Mutation 15 | } 16 | scalar JSON 17 | type Mutation { 18 | stripe: StripeMutations! 19 | } 20 | type Query { 21 | stripe: Stripe! 22 | } 23 | type Stripe { 24 | connectedAccount(id: String!): StripeConnectedAccount! 25 | connectedAccounts: StripeConnectedAccounts! 26 | customer(id: String!): StripeCustomer! 27 | customers(email: String, endingBefore: String, limit: Int, startingAfter: String): StripeCustomers! 28 | } 29 | type StripeAddress { 30 | city: String 31 | country: String 32 | line1: String 33 | line2: String 34 | postalCode: String 35 | state: String 36 | } 37 | type StripeBillingPortalSession { 38 | created: Int! 39 | id: String! 40 | livemode: Boolean! 41 | locale: String 42 | object: String! 43 | returnUrl: String 44 | url: String! 45 | } 46 | type StripeCharge { 47 | amount: Int! 48 | amountCaptured: Int! 49 | amountRefunded: Int! 50 | application: StripeConnectedAccount 51 | applicationFeeAmount: Int 52 | billingDetails: JSON 53 | calculatedStatementDescriptor: String 54 | captured: Boolean! 55 | created: Int 56 | currency: String! 57 | customer: String! 58 | description: String 59 | disputed: Boolean! 60 | failureCode: String 61 | fraudDetails: JSON 62 | id: String! 63 | invoice: StripeInvoice 64 | livemode: Boolean! 65 | metadata: JSON 66 | outcome: JSON 67 | paid: Boolean! 68 | paymentIntent: String 69 | paymentMethod: String 70 | paymentMethodDetails: JSON 71 | receiptEmail: String 72 | receiptNumber: String 73 | receiptUrl: String 74 | refunded: Boolean! 75 | refunds: JSON 76 | shipping: JSON 77 | statementDescriptor: String 78 | statementDescriptorSuffix: String 79 | status: String! 80 | transferData: JSON 81 | transferGroup: String 82 | } 83 | type StripeCharges { 84 | data: [StripeCharge!]! 85 | hasMore: Boolean! 86 | object: String! 87 | url: String! 88 | } 89 | type StripeConnectedAccount { 90 | businessProfile: JSON! 91 | businessType: String 92 | capabilities: JSON! 93 | chargesEnabled: Boolean! 94 | company: JSON! 95 | controller: JSON 96 | country: String 97 | created: Int 98 | defaultCurrency: String 99 | detailsSubmitted: Boolean! 100 | email: String 101 | externalAccounts: JSON! 102 | futureRequirements: JSON! 103 | id: String! 104 | individual: JSON! 105 | metadata: JSON! 106 | object: String! 107 | payoutsEnabled: Boolean! 108 | requirements: JSON! 109 | settings: JSON! 110 | tosAcceptance: JSON! 111 | } 112 | type StripeConnectedAccounts { 113 | data: [StripeConnectedAccount!]! 114 | hasMore: Boolean! 115 | object: String! 116 | url: String! 117 | } 118 | type StripeCustomer { 119 | address: StripeAddress 120 | balance: Int! 121 | charges: StripeCharges! 122 | created: Int! 123 | currency: String 124 | delinquent: Boolean 125 | description: String 126 | email: String 127 | id: String! 128 | invoicePrefix: String 129 | invoices: StripeInvoices! 130 | livemode: Boolean! 131 | metadata: JSON! 132 | name: String 133 | nextInvoiceSequence: Int 134 | object: String! 135 | paymentIntents: StripePaymentIntents! 136 | paymentMethods(endingBefore: String, limit: Int, startingAfter: String, type: StripePaymentMethodTypes! = card): StripePaymentMethods! 137 | phone: String 138 | preferredLocales: [String!] 139 | shipping: StripeCustomerShipping 140 | subscriptions: StripeSubscriptions! 141 | tax: StripeCustomerTax 142 | } 143 | type StripeCustomerShipping { 144 | address: StripeAddress 145 | carrier: String 146 | name: String 147 | phone: String 148 | trackingNumber: String 149 | } 150 | type StripeCustomerTax { 151 | ipAddress: String 152 | location: StripeCustomerTaxLocation 153 | } 154 | type StripeCustomerTaxLocation { 155 | country: String! 156 | state: String 157 | } 158 | type StripeCustomers { 159 | data: [StripeCustomer!]! 160 | hasMore: Boolean! 161 | object: String! 162 | url: String! 163 | } 164 | type StripeInvoice { 165 | accountCountry: String 166 | accountName: String 167 | amountDue: Int! 168 | amountPaid: Int! 169 | amountRemaining: Int! 170 | application: StripeConnectedAccount 171 | applicationFeeAmount: Int 172 | attemptCount: Int! 173 | attempted: Boolean! 174 | autoAdvance: Boolean 175 | automaticTax: StripeInvoiceAutomaticTax! 176 | billingReason: String 177 | collectionMethod: String 178 | created: Int! 179 | currency: String! 180 | customer: String! 181 | customerAddress: StripeAddress 182 | customerEmail: String 183 | customerName: String 184 | customerPhone: String 185 | customerShipping: StripeInvoiceCustomerShipping 186 | customerTaxExempt: String 187 | customerTaxIds: [StripeInvoiceCustomerTaxId!] 188 | defaultPaymentMethod: StripePaymentMethod 189 | description: String 190 | dueDate: Int 191 | endingBalance: Int 192 | footer: String 193 | hostedInvoiceUrl: String 194 | id: String! 195 | invoicePdf: String 196 | lines: StripeInvoiceLineItems! 197 | livemode: Boolean! 198 | metadata: JSON! 199 | nextPaymentAttempt: Int 200 | number: String 201 | object: String! 202 | paid: Boolean! 203 | paidOutOfBand: Boolean! 204 | periodEnd: Int! 205 | periodStart: Int! 206 | postPaymentCreditNotesAmount: Int! 207 | prePaymentCreditNotesAmount: Int! 208 | receiptNumber: String 209 | renderingOptions: StripeInvoiceRenderingOptions 210 | startingBalance: Int! 211 | statementDescriptor: String 212 | status: String 213 | statusTransition: StripeInvoiceStatusTransitions 214 | subscription: StripeSubscription 215 | subscriptionProrationDate: Int 216 | subtotal: Int! 217 | subtotalExcludingTax: Int 218 | tax: Int 219 | total: Int! 220 | totalExcludingTax: Int 221 | webhooksDeliveredAt: Int 222 | } 223 | type StripeInvoiceAutomaticTax { 224 | enabled: Boolean! 225 | status: String 226 | } 227 | type StripeInvoiceCustomerShipping { 228 | address: StripeAddress 229 | carrier: String 230 | name: String 231 | phone: String 232 | trackingNumber: String 233 | } 234 | type StripeInvoiceCustomerTaxId { 235 | type: String! 236 | value: String 237 | } 238 | type StripeInvoiceLineItem { 239 | amount: Int! 240 | amountExcludingTax: Int 241 | currency: String! 242 | description: String 243 | discountable: Boolean! 244 | id: String! 245 | invoiceItem: String 246 | livemode: Boolean! 247 | metadata: JSON! 248 | object: String! 249 | period: StripeInvoiceLineItemPeriod! 250 | plan: StripePlan 251 | price: StripePrice 252 | proration: Boolean! 253 | quantity: Int 254 | taxAmount: [StripeInvoiceLineItemTaxAmount!] 255 | taxRates: [StripeTaxRate!] 256 | type: String! 257 | unitAmountExcludingTax: String 258 | } 259 | type StripeInvoiceLineItemPeriod { 260 | end: Int! 261 | start: Int! 262 | } 263 | type StripeInvoiceLineItemTaxAmount { 264 | amount: Int! 265 | inclusive: Boolean! 266 | } 267 | type StripeInvoiceLineItems { 268 | data: [StripeInvoiceLineItem!]! 269 | hasMore: Boolean! 270 | object: String! 271 | url: String! 272 | } 273 | type StripeInvoiceRenderingOptions { 274 | amountTaxDisplay: String 275 | } 276 | type StripeInvoiceStatusTransitions { 277 | finalizedAt: Int 278 | markedUncollectibleAt: Int 279 | paidAt: Int 280 | voidedAt: Int 281 | } 282 | type StripeInvoices { 283 | data: [StripeInvoice!]! 284 | hasMore: Boolean! 285 | object: String! 286 | url: String! 287 | } 288 | type StripeMutations { 289 | createBillingPortalSession(configuration: String, customer: String!, locale: String, returnUrl: String): StripeBillingPortalSession! 290 | } 291 | type StripePaymentIntent { 292 | amount: Int! 293 | amountCapturable: Int! 294 | amountDetails: JSON 295 | amountReceived: Int! 296 | applicationFeeAmount: Int 297 | canceledAt: Int 298 | cancellationReason: String 299 | created: Int 300 | currency: String! 301 | customer: String! 302 | description: String 303 | id: String! 304 | invoice: StripeInvoice 305 | metadata: JSON 306 | object: String! 307 | paymentMethodTypes: [String!]! 308 | receiptEmail: String 309 | statementDescriptor: String 310 | statementDescriptorSuffix: String 311 | status: String! 312 | transferGroup: String 313 | } 314 | type StripePaymentIntents { 315 | data: [StripePaymentIntent!]! 316 | hasMore: Boolean! 317 | object: String! 318 | url: String! 319 | } 320 | type StripePaymentMethod { 321 | billingDetails: StripePaymentMethodBillingDetails 322 | card: StripePaymentMethodCard 323 | created: Int! 324 | customer: String 325 | id: String! 326 | livemode: Boolean! 327 | metadata: JSON! 328 | object: String! 329 | type: StripePaymentMethodTypes! 330 | } 331 | type StripePaymentMethodBillingDetails { 332 | address: StripeAddress 333 | email: String 334 | name: String 335 | phone: String 336 | } 337 | type StripePaymentMethodCard { 338 | brand: String! 339 | check: StripePaymentMethodCardChecks 340 | country: String 341 | description: String 342 | expMonth: Int! 343 | expYear: Int! 344 | fingerprint: String 345 | funding: String! 346 | iin: String 347 | issuer: String 348 | last4: String! 349 | networks: StripePaymentMethodCardNetworks 350 | threeDSecureUsage: StripePaymentMethodCardThreeDSecureUsage 351 | wallet: StripePaymentMethodCardWallet 352 | } 353 | type StripePaymentMethodCardChecks { 354 | addressLine1Check: String 355 | addressPostalCodeCheck: String 356 | cvcCheck: String 357 | } 358 | type StripePaymentMethodCardNetworks { 359 | available: [String!]! 360 | preferred: String 361 | } 362 | type StripePaymentMethodCardThreeDSecureUsage { 363 | supported: Boolean! 364 | } 365 | type StripePaymentMethodCardWallet { 366 | dynamicLast4: String 367 | masterpass: StripePaymentMethodCardWalletMasterpass 368 | type: StripePaymentMethodCardWalletType! 369 | visaCheckout: StripePaymentMethodCardWalletVisaCheckout 370 | } 371 | type StripePaymentMethodCardWalletMasterpass { 372 | billingAddress: StripeAddress 373 | email: String 374 | name: String 375 | shippinAddress: StripeAddress 376 | } 377 | type StripePaymentMethodCardWalletVisaCheckout { 378 | billingAddress: StripeAddress 379 | email: String 380 | name: String 381 | shippinAddress: StripeAddress 382 | } 383 | type StripePaymentMethods { 384 | data: [StripePaymentMethod!]! 385 | hasMore: Boolean! 386 | object: String! 387 | url: String! 388 | } 389 | type StripePlan { 390 | active: Boolean! 391 | aggregateUsage: String 392 | amount: Int 393 | amountDecimal: String 394 | billingScheme: String! 395 | created: Int! 396 | currency: String! 397 | id: String! 398 | interval: String! 399 | intervalCount: Int! 400 | livemode: Boolean! 401 | metadata: JSON 402 | nickname: String 403 | object: String! 404 | product: StripeProduct 405 | tiersMode: String 406 | transformUsage: StripePlanTransformUsage 407 | trialPeriodDays: Int 408 | usageType: String! 409 | } 410 | type StripePlanTransformUsage { 411 | divideBy: Int! 412 | round: String! 413 | } 414 | type StripePrice { 415 | active: Boolean! 416 | billingScheme: String! 417 | created: Int! 418 | currency: String! 419 | id: String! 420 | livemode: Boolean! 421 | lookupKey: String 422 | metadata: JSON 423 | nickname: String 424 | object: String! 425 | product: StripeProduct! 426 | tiersMode: String 427 | type: String! 428 | unitAmount: Int 429 | unitAmountDecimal: String 430 | } 431 | type StripeProduct { 432 | active: Boolean! 433 | attributes: [String!] 434 | caption: String 435 | created: Int! 436 | deactivateOn: [String!] 437 | defaultPrice: StripePrice 438 | description: String 439 | id: String! 440 | images: [String!] 441 | livemode: Boolean! 442 | metadata: JSON 443 | name: String! 444 | object: String! 445 | sippable: Boolean 446 | statementDescriptor: String 447 | type: String! 448 | unitLabel: String 449 | updated: Int! 450 | url: String 451 | } 452 | type StripeSubscription { 453 | applicationFeePercent: Float 454 | automaticTax: StripeSubscriptionAutomaticTax! 455 | billingCycleAnchor: Int! 456 | billingThresholds: StripeSubscriptionBillingThresholds 457 | cancelAt: Int 458 | cancelAtPeriodEnd: Boolean! 459 | canceledAt: Int 460 | collectionMethods: String! 461 | created: Int! 462 | currency: String! 463 | currentPeriodEnd: Int! 464 | currentPeriodStart: Int! 465 | customer: String! 466 | daysUntilDue: Int 467 | defaultPaymentMethod: StripePaymentMethod 468 | defaultTaxRates: [StripeTaxRate!] 469 | description: String 470 | endedAt: Int 471 | id: String! 472 | items: StripeSubscriptionItems! 473 | latestInvoice: StripeInvoice 474 | livemode: Boolean! 475 | metadata: JSON! 476 | nextPendingInvoiceItemInvoice: Int 477 | object: String! 478 | pauseCollection: StripeSubscriptionPauseCollection 479 | startDate: Int! 480 | status: String! 481 | testClock: StripeTestClock 482 | trialEnd: Int 483 | trialStart: Int 484 | } 485 | type StripeSubscriptionAutomaticTax { 486 | enabled: Boolean! 487 | } 488 | type StripeSubscriptionBillingThresholds { 489 | amountGte: Int 490 | resetBillingCycleAnchor: Boolean 491 | } 492 | type StripeSubscriptionItem { 493 | billingThresholds: StripeSubscriptionItemBillingThresholds 494 | created: Int! 495 | id: String! 496 | metadata: JSON! 497 | object: String! 498 | plan: StripePlan! 499 | price: StripePrice! 500 | quantity: Int 501 | subscription: String! 502 | } 503 | type StripeSubscriptionItemBillingThresholds { 504 | usageGte: Int 505 | } 506 | type StripeSubscriptionItems { 507 | data: [StripeSubscriptionItem!]! 508 | hasMore: Boolean! 509 | object: String! 510 | url: String! 511 | } 512 | type StripeSubscriptionPauseCollection { 513 | behavior: String! 514 | resumesAt: Int 515 | } 516 | type StripeSubscriptions { 517 | data: [StripeSubscription!]! 518 | hasMore: Boolean! 519 | object: String! 520 | url: String! 521 | } 522 | type StripeTaxRate { 523 | active: Boolean! 524 | country: String 525 | created: Int! 526 | description: String 527 | displayName: String! 528 | id: String! 529 | inclusive: Boolean! 530 | jurisdiction: String 531 | livemode: Boolean! 532 | metadata: JSON 533 | object: String! 534 | percentage: Float! 535 | state: String 536 | taxType: String 537 | } 538 | type StripeTestClock { 539 | created: Int! 540 | deletesAfter: Int! 541 | frozenTime: Int! 542 | id: String! 543 | livemode: Boolean! 544 | name: String 545 | object: String! 546 | status: String! 547 | } 548 | enum StripePaymentMethodCardWalletType { 549 | amex_express_checkout 550 | apple_pay 551 | google_pay 552 | masterpass 553 | samsung_pay 554 | visa_checkout 555 | } 556 | enum StripePaymentMethodTypes { 557 | acss_debit 558 | affirm 559 | afterpay_clearpay 560 | alipay 561 | au_becs_debit 562 | bacs_debit 563 | bancontact 564 | blik 565 | boleto 566 | card 567 | card_present 568 | customer_balance 569 | eps 570 | fpx 571 | giropay 572 | grabpay 573 | ideal 574 | klarna 575 | konbini 576 | link 577 | oxxo 578 | p24 579 | paynow 580 | promptpay 581 | sepa_debit 582 | sofort 583 | us_bank_account 584 | wechat_pay 585 | } 586 | -------------------------------------------------------------------------------- /nhost/metadata/rest_endpoints.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /nhost/metadata/version.yaml: -------------------------------------------------------------------------------- 1 | version: 3 2 | -------------------------------------------------------------------------------- /nhost/migrations/default/1669308895353_create_table_public_profile/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE "public"."profile"; 2 | -------------------------------------------------------------------------------- /nhost/migrations/default/1669308895353_create_table_public_profile/up.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS pgcrypto; 2 | 3 | CREATE TABLE "public"."profiles" ( 4 | "id" uuid NOT NULL DEFAULT gen_random_uuid(), 5 | "stripe_customer_id" text, 6 | PRIMARY KEY ("id"), 7 | FOREIGN KEY ("id") REFERENCES "auth"."users"("id") ON UPDATE RESTRICT ON DELETE CASCADE 8 | ); 9 | -------------------------------------------------------------------------------- /nhost/migrations/default/1669316801508_create_table_public_products/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE "public"."products"; 2 | -------------------------------------------------------------------------------- /nhost/migrations/default/1669316801508_create_table_public_products/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "public"."products" ( 2 | "id" uuid NOT NULL DEFAULT gen_random_uuid(), 3 | "created_at" timestamptz NOT NULL DEFAULT NOW(), 4 | "updated_at" timestamptz NOT NULL DEFAULT NOW(), 5 | "name" text NOT NULL, 6 | "price_id" text NOT NULL, 7 | "amount" bigint NOT NULL, 8 | "currency" text NOT NULL, 9 | PRIMARY KEY ("id") 10 | ); 11 | 12 | CREATE 13 | OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"() RETURNS TRIGGER AS $$ DECLARE _new record; 14 | 15 | BEGIN _new := NEW; 16 | 17 | _new."updated_at" = NOW(); 18 | 19 | RETURN _new; 20 | 21 | END; 22 | 23 | $$ LANGUAGE plpgsql; 24 | 25 | CREATE TRIGGER "set_public_products_updated_at" BEFORE 26 | UPDATE 27 | ON "public"."products" FOR EACH ROW EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"(); 28 | 29 | COMMENT ON TRIGGER "set_public_products_updated_at" ON "public"."products" IS 'trigger to set value of column "updated_at" to current timestamp on row update'; 30 | 31 | CREATE EXTENSION IF NOT EXISTS pgcrypto; -------------------------------------------------------------------------------- /nhost/migrations/default/1669317002587_alter_table_public_products_alter_column_price_id/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."products" drop constraint "products_price_id_key"; 2 | -------------------------------------------------------------------------------- /nhost/migrations/default/1669317002587_alter_table_public_products_alter_column_price_id/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE 2 | "public"."products" 3 | ADD 4 | CONSTRAINT "products_price_id_key" UNIQUE ("price_id"); -------------------------------------------------------------------------------- /nhost/migrations/default/1669325785961_run_sql_migration/down.sql: -------------------------------------------------------------------------------- 1 | -- Could not auto-generate a down migration. 2 | -- Please write an appropriate down migration for the SQL below: 3 | -- ALTER TABLE public.products ADD COLUMN description text DEFAULT ''; 4 | -------------------------------------------------------------------------------- /nhost/migrations/default/1669325785961_run_sql_migration/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE 2 | public.products 3 | ADD 4 | COLUMN description text DEFAULT ''; -------------------------------------------------------------------------------- /nhost/migrations/default/1669975961205_alter_table_public_profiles_add_column_plan_id/down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE public.profiles DROP COLUMN IF EXISTS plan_id; 2 | -------------------------------------------------------------------------------- /nhost/migrations/default/1669975961205_alter_table_public_profiles_add_column_plan_id/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE 2 | public.profiles 3 | ADD 4 | plan_id uuid; 5 | 6 | ALTER TABLE 7 | public.profiles 8 | ADD 9 | CONSTRAINT profiles_plan_id_fkey FOREIGN KEY (plan_id) REFERENCES public.products (id) ON UPDATE RESTRICT ON DELETE RESTRICT; -------------------------------------------------------------------------------- /nhost/migrations/default/1669975970500_alter_table_public_products_alter_column_price_id/down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE public.products RENAME COLUMN stripe_price_id TO price_id; 2 | -------------------------------------------------------------------------------- /nhost/migrations/default/1669975970500_alter_table_public_products_alter_column_price_id/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE 2 | public.products RENAME COLUMN price_id TO stripe_price_id; -------------------------------------------------------------------------------- /nhost/migrations/default/1669976516108_rename_table_public_products/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."plans" rename to "products"; 2 | -------------------------------------------------------------------------------- /nhost/migrations/default/1669976516108_rename_table_public_products/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE 2 | "public"."products" RENAME TO "plans"; -------------------------------------------------------------------------------- /nhost/seeds/default/001-plans.sql: -------------------------------------------------------------------------------- 1 | -- update the price_id with the price id from stripe 2 | -- amount is in cents 3 | INSERT INTO 4 | public.plans (name, stripe_price_id, amount, currency, description) 5 | VALUES 6 | ( 7 | 'Hobby', 8 | 'price_1M82BOCCF9wuB4fX3BplVWB9', 9 | 1200, 10 | 'USD', 11 | 'Hobby Description' 12 | ), 13 | ( 14 | 'Startup', 15 | 'price_1M82BuCCF9wuB4fXhdtUU6Pk', 16 | 2400, 17 | 'USD', 18 | 'Startup Description' 19 | ), 20 | ( 21 | 'Pro', 22 | 'price_1M82CACCF9wuB4fXZ019vYFb', 23 | 3200, 24 | 'USD', 25 | 'Pro Description' 26 | ), 27 | ( 28 | 'Enterprise', 29 | 'price_1M82CTCCF9wuB4fXA9Sg5jyT', 30 | 4800, 31 | 'USD', 32 | 'Enterprise Description' 33 | ); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-subscription-payments", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "scripts": { 6 | "dev": "next", 7 | "build": "next build", 8 | "start": "next start", 9 | "codegen": "graphql-codegen --config graphql.config.yaml --errors-only", 10 | "stripe:listen": "stripe listen --forward-to=localhost:1337/v1/functions/webhook/stripe" 11 | }, 12 | "dependencies": { 13 | "@nhost/nextjs": "^1.13.3", 14 | "@nhost/stripe-graphql-js": "^0.0.6", 15 | "@stripe/stripe-js": "1.22.0", 16 | "@tanstack/react-query": "^4.16.1", 17 | "classnames": "2.3.1", 18 | "date-fns": "^2.29.3", 19 | "graphql": "^16.6.0", 20 | "graphql-request": "^5.0.0", 21 | "graphql-tag": "^2.12.6", 22 | "jsonwebtoken": "^8.5.1", 23 | "next": "^12.2.5", 24 | "react": "17.0.2", 25 | "react-dom": "17.0.2", 26 | "react-merge-refs": "1.1.0", 27 | "stripe": "8.201.0", 28 | "swr": "1.2.0", 29 | "tailwindcss": "3.0.18" 30 | }, 31 | "devDependencies": { 32 | "@graphql-codegen/cli": "^2.14.0", 33 | "@graphql-codegen/typescript-graphql-request": "^4.5.8", 34 | "@graphql-codegen/typescript-operations": "^2.5.7", 35 | "@graphql-codegen/typescript-react-query": "^4.0.6", 36 | "@types/classnames": "2.3.1", 37 | "@types/express": "^4.17.14", 38 | "@types/jsonwebtoken": "^8.5.9", 39 | "@types/node": "^17.0.13", 40 | "@types/react": "^17.0.38", 41 | "autoprefixer": "^10.4.2", 42 | "postcss": "8.4.5", 43 | "prettier": "2.5.1", 44 | "typescript": "^4.5.5" 45 | }, 46 | "prettier": { 47 | "arrowParens": "always", 48 | "singleQuote": true, 49 | "tabWidth": 2, 50 | "trailingComma": "none" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import 'styles/main.css'; 2 | import 'styles/chrome-bug.css'; 3 | 4 | import React from 'react'; 5 | 6 | import Layout from 'components/Layout'; 7 | import { AppProps } from 'next/app'; 8 | 9 | import { NhostNextProvider, SignedIn, SignedOut } from '@nhost/nextjs'; 10 | import { nhost } from '@/utils/nhost'; 11 | import { QueryClientProvider } from '@tanstack/react-query'; 12 | import { queryClient } from '@/utils/react-query-client'; 13 | import SignIn from './signin'; 14 | 15 | export default function MyApp({ Component, pageProps }: AppProps) { 16 | return ( 17 |{description}
26 | {children} 27 |98 | We partnered with Stripe for a simplified billing. 99 |
100 |113 | Manage your subscription on Stripe. 114 |
115 | 125 |{userData?.email}
182 |