diff --git a/apps/web/package.json b/apps/web/package.json index 22a75128f..8e7dd2be7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,6 +11,7 @@ "copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs" }, "dependencies": { + "@documenso/ee": "*", "@documenso/lib": "*", "@documenso/prisma": "*", "@documenso/tailwind-config": "*", diff --git a/apps/web/src/app/(dashboard)/settings/billing/page.tsx b/apps/web/src/app/(dashboard)/settings/billing/page.tsx index 256f682fb..e9966b9ac 100644 --- a/apps/web/src/app/(dashboard)/settings/billing/page.tsx +++ b/apps/web/src/app/(dashboard)/settings/billing/page.tsx @@ -1,8 +1,14 @@ +import Link from 'next/link'; import { redirect } from 'next/navigation'; +import { createCustomer } from '@documenso/ee/server-only/stripe/create-customer'; +import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session'; +import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id'; +import { SubscriptionStatus } from '@documenso/prisma/client'; +import { Button } from '@documenso/ui/primitives/button'; -import { PasswordForm } from '~/components/forms/password'; +import { LocaleDate } from '~/components/formatter/locale-date'; import { getServerComponentFlag } from '~/helpers/get-server-component-feature-flag'; export default async function BillingSettingsPage() { @@ -15,17 +21,55 @@ export default async function BillingSettingsPage() { redirect('/settings/profile'); } + let subscription = await getSubscriptionByUserId({ userId: user.id }); + + // If we don't have a customer record, create one as well as an empty subscription. + if (!subscription?.customerId) { + subscription = await createCustomer({ user }); + } + + let billingPortalUrl = ''; + + if (subscription?.customerId) { + billingPortalUrl = await getPortalSession({ + customerId: subscription.customerId, + returnUrl: `${process.env.NEXT_PUBLIC_SITE_URL}/settings/billing`, + }); + } + return (

Billing

- Here you can update and manage your subscription. + Your subscription is{' '} + {subscription.status !== SubscriptionStatus.INACTIVE ? 'active' : 'inactive'}. + {subscription?.periodEnd && ( + <> + {' '} + Your next payment is due on{' '} + + + + . + + )}


- + {billingPortalUrl && ( + + )} + + {!billingPortalUrl && ( +

+ You do not currently have a customer record, this should not happen. Please contact + support for assistance. +

+ )}
); } diff --git a/package-lock.json b/package-lock.json index c6c404669..12c957494 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,7 @@ "version": "0.1.0", "license": "AGPL-3.0", "dependencies": { + "@documenso/ee": "*", "@documenso/lib": "*", "@documenso/prisma": "*", "@documenso/tailwind-config": "*", @@ -1076,6 +1077,10 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@documenso/ee": { + "resolved": "packages/ee", + "link": true + }, "node_modules/@documenso/email": { "resolved": "packages/email", "link": true @@ -16313,6 +16318,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "packages/ee": { + "version": "1.0.0", + "license": "COMMERCIAL", + "dependencies": { + "@documenso/lib": "*", + "@documenso/prisma": "*" + } + }, "packages/email": { "name": "@documenso/email", "version": "1.0.0", @@ -16349,7 +16362,7 @@ "packages/lib": { "name": "@documenso/lib", "version": "1.0.0", - "license": "MIT", + "license": "SEE LICENSE IN LICENSE", "dependencies": { "@documenso/email": "*", "@documenso/prisma": "*", diff --git a/packages/ee/LICENSE b/packages/ee/LICENSE new file mode 100644 index 000000000..cc97e3a10 --- /dev/null +++ b/packages/ee/LICENSE @@ -0,0 +1,40 @@ +The Documenso Commercial License (the “Commercial License”) +Copyright (c) 2023 Documenso, Inc + +With regard to the Documenso Software: + +This software and associated documentation files (the "Software") may only be +used in production, if you (and any entity that you represent) have agreed to, +and are in compliance with, an agreement governing +the use of the Software, as mutually agreed by you and Documenso, Inc ("Documenso"), +and otherwise have a valid Documenso Enterprise Edition subscription ("Commercial Subscription"). +Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. +You agree that Documenso and/or its licensors (as applicable) retain all right, title and interest in +and to all such modifications and/or patches, and all such modifications and/or +patches may only be used, copied, modified, displayed, distributed, or otherwise +exploited with a valid Commercial Subscription for the correct number of hosts. +Notwithstanding the foregoing, you may copy and modify the Software for development +and testing purposes, without requiring a subscription. You agree that Documenso and/or +its licensors (as applicable) retain all right, title and interest in and to all such +modifications. You are not granted any other rights beyond what is expressly stated herein. +Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense, +and/or sell the Software. + +This Commercial License applies only to the part of this Software that is not distributed under +the AGPLv3 license. Any part of this Software distributed under the MIT license or which +is served client-side as an image, font, cascading stylesheet (CSS), file which produces +or is compiled, arranged, augmented, or combined into client-side JavaScript, in whole or +in part, is copyrighted under the AGPLv3 license. The full text of this Commercial License shall +be included in all copies or substantial portions of the Software. + +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. + +For all third party components incorporated into the Documenso Software, those +components are licensed under the original license provided by the owner of the +applicable component. diff --git a/packages/ee/index.ts b/packages/ee/index.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/packages/ee/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/ee/package.json b/packages/ee/package.json new file mode 100644 index 000000000..4c810a235 --- /dev/null +++ b/packages/ee/package.json @@ -0,0 +1,17 @@ +{ + "name": "@documenso/ee", + "version": "1.0.0", + "main": "./index.ts", + "types": "./index.ts", + "license": "COMMERCIAL", + "files": [ + "client-only/", + "server-only/", + "universal/" + ], + "scripts": {}, + "dependencies": { + "@documenso/lib": "*", + "@documenso/prisma": "*" + } +} diff --git a/packages/ee/server-only/stripe/create-customer.ts b/packages/ee/server-only/stripe/create-customer.ts new file mode 100644 index 000000000..175298d0b --- /dev/null +++ b/packages/ee/server-only/stripe/create-customer.ts @@ -0,0 +1,31 @@ +import { stripe } from '@documenso/lib/server-only/stripe'; +import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id'; +import { prisma } from '@documenso/prisma'; +import { User } from '@documenso/prisma/client'; + +export type CreateCustomerOptions = { + user: User; +}; + +export const createCustomer = async ({ user }: CreateCustomerOptions) => { + const existingSubscription = await getSubscriptionByUserId({ userId: user.id }); + + if (existingSubscription) { + throw new Error('User already has a subscription'); + } + + const customer = await stripe.customers.create({ + name: user.name ?? undefined, + email: user.email, + metadata: { + userId: user.id, + }, + }); + + return await prisma.subscription.create({ + data: { + userId: user.id, + customerId: customer.id, + }, + }); +}; diff --git a/packages/ee/server-only/stripe/get-portal-session.ts b/packages/ee/server-only/stripe/get-portal-session.ts new file mode 100644 index 000000000..310cc1e47 --- /dev/null +++ b/packages/ee/server-only/stripe/get-portal-session.ts @@ -0,0 +1,19 @@ +'use server'; + +import { stripe } from '@documenso/lib/server-only/stripe'; + +export type GetPortalSessionOptions = { + customerId: string; + returnUrl: string; +}; + +export const getPortalSession = async ({ customerId, returnUrl }: GetPortalSessionOptions) => { + 'use server'; + + const session = await stripe.billingPortal.sessions.create({ + customer: customerId, + return_url: returnUrl, + }); + + return session.url; +}; diff --git a/packages/ee/tsconfig.json b/packages/ee/tsconfig.json new file mode 100644 index 000000000..0f63d1612 --- /dev/null +++ b/packages/ee/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@documenso/tsconfig/react-library.json", + "include": ["**/*.ts", "**/*.tsx", "**/*.d.ts"], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/packages/lib/server-only/subscription/get-subscription-by-user-id.ts b/packages/lib/server-only/subscription/get-subscription-by-user-id.ts new file mode 100644 index 000000000..19932669a --- /dev/null +++ b/packages/lib/server-only/subscription/get-subscription-by-user-id.ts @@ -0,0 +1,15 @@ +'use server'; + +import { prisma } from '@documenso/prisma'; + +export type GetSubscriptionByUserIdOptions = { + userId: number; +}; + +export const getSubscriptionByUserId = ({ userId }: GetSubscriptionByUserIdOptions) => { + return prisma.subscription.findFirst({ + where: { + userId, + }, + }); +};