mirror of
https://github.com/documenso/documenso.git
synced 2025-11-17 10:11:35 +10:00
Merge pull request #384 from documenso/feat/stripe-free-tier
feat: add Stripe free tier subscription
This commit is contained in:
31
packages/ee/server-only/stripe/get-checkout-session.ts
Normal file
31
packages/ee/server-only/stripe/get-checkout-session.ts
Normal file
@ -0,0 +1,31 @@
|
||||
'use server';
|
||||
|
||||
import { stripe } from '@documenso/lib/server-only/stripe';
|
||||
|
||||
export type GetCheckoutSessionOptions = {
|
||||
customerId: string;
|
||||
priceId: string;
|
||||
returnUrl: string;
|
||||
};
|
||||
|
||||
export const getCheckoutSession = async ({
|
||||
customerId,
|
||||
priceId,
|
||||
returnUrl,
|
||||
}: GetCheckoutSessionOptions) => {
|
||||
'use server';
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
customer: customerId,
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
success_url: `${returnUrl}?success=true`,
|
||||
cancel_url: `${returnUrl}?canceled=true`,
|
||||
});
|
||||
|
||||
return session.url;
|
||||
};
|
||||
19
packages/ee/server-only/stripe/get-customer.ts
Normal file
19
packages/ee/server-only/stripe/get-customer.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { stripe } from '@documenso/lib/server-only/stripe';
|
||||
|
||||
export const getStripeCustomerByEmail = async (email: string) => {
|
||||
const foundStripeCustomers = await stripe.customers.list({
|
||||
email,
|
||||
});
|
||||
|
||||
return foundStripeCustomers.data[0] ?? null;
|
||||
};
|
||||
|
||||
export const getStripeCustomerById = async (stripeCustomerId: string) => {
|
||||
try {
|
||||
const stripeCustomer = await stripe.customers.retrieve(stripeCustomerId);
|
||||
|
||||
return !stripeCustomer.deleted ? stripeCustomer : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
40
packages/ee/server-only/stripe/get-prices-by-interval.ts
Normal file
40
packages/ee/server-only/stripe/get-prices-by-interval.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import Stripe from 'stripe';
|
||||
|
||||
import { stripe } from '@documenso/lib/server-only/stripe';
|
||||
|
||||
// Utility type to handle usage of the `expand` option.
|
||||
type PriceWithProduct = Stripe.Price & { product: Stripe.Product };
|
||||
|
||||
export type PriceIntervals = Record<Stripe.Price.Recurring.Interval, PriceWithProduct[]>;
|
||||
|
||||
export const getPricesByInterval = async () => {
|
||||
const { data: prices } = await stripe.prices.search({
|
||||
query: `active:'true' type:'recurring'`,
|
||||
expand: ['data.product'],
|
||||
limit: 100,
|
||||
});
|
||||
|
||||
const intervals: PriceIntervals = {
|
||||
day: [],
|
||||
week: [],
|
||||
month: [],
|
||||
year: [],
|
||||
};
|
||||
|
||||
// Add each price to the correct interval.
|
||||
for (const price of prices) {
|
||||
if (price.recurring?.interval) {
|
||||
// We use `expand` to get the product, but it's not typed as part of the Price type.
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
intervals[price.recurring.interval].push(price as PriceWithProduct);
|
||||
}
|
||||
}
|
||||
|
||||
// Order all prices by unit_amount.
|
||||
intervals.day.sort((a, b) => Number(a.unit_amount) - Number(b.unit_amount));
|
||||
intervals.week.sort((a, b) => Number(a.unit_amount) - Number(b.unit_amount));
|
||||
intervals.month.sort((a, b) => Number(a.unit_amount) - Number(b.unit_amount));
|
||||
intervals.year.sort((a, b) => Number(a.unit_amount) - Number(b.unit_amount));
|
||||
|
||||
return intervals;
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
/// <reference types="./stripe.d.ts" />
|
||||
import Stripe from 'stripe';
|
||||
|
||||
export const stripe = new Stripe(process.env.NEXT_PRIVATE_STRIPE_API_KEY ?? '', {
|
||||
|
||||
7
packages/lib/server-only/stripe/stripe.d.ts
vendored
Normal file
7
packages/lib/server-only/stripe/stripe.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
declare module 'stripe' {
|
||||
namespace Stripe {
|
||||
interface Product {
|
||||
features?: Array<{ name: string }>;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
packages/lib/universal/stripe/to-human-price.ts
Normal file
3
packages/lib/universal/stripe/to-human-price.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const toHumanPrice = (price: number) => {
|
||||
return Number(price / 100).toFixed(2);
|
||||
};
|
||||
@ -0,0 +1,17 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[userId]` on the table `Subscription` will be added. If there are existing duplicate values, this will fail.
|
||||
- Made the column `customerId` on table `Subscription` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
|
||||
DELETE FROM "Subscription"
|
||||
WHERE "customerId" IS NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Subscription" ADD COLUMN "cancelAtPeriodEnd" BOOLEAN NOT NULL DEFAULT false,
|
||||
ALTER COLUMN "customerId" SET NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Subscription_userId_key" ON "Subscription"("userId");
|
||||
@ -51,15 +51,16 @@ enum SubscriptionStatus {
|
||||
}
|
||||
|
||||
model Subscription {
|
||||
id Int @id @default(autoincrement())
|
||||
status SubscriptionStatus @default(INACTIVE)
|
||||
planId String?
|
||||
priceId String?
|
||||
customerId String?
|
||||
periodEnd DateTime?
|
||||
userId Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
id Int @id @default(autoincrement())
|
||||
status SubscriptionStatus @default(INACTIVE)
|
||||
planId String?
|
||||
priceId String?
|
||||
customerId String
|
||||
periodEnd DateTime?
|
||||
userId Int @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
cancelAtPeriodEnd Boolean @default(false)
|
||||
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
|
||||
1
packages/tsconfig/process-env.d.ts
vendored
1
packages/tsconfig/process-env.d.ts
vendored
@ -10,6 +10,7 @@ declare namespace NodeJS {
|
||||
|
||||
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID: string;
|
||||
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID: string;
|
||||
NEXT_PUBLIC_STRIPE_FREE_PLAN_ID?: string;
|
||||
|
||||
NEXT_PRIVATE_STRIPE_API_KEY: string;
|
||||
NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET: string;
|
||||
|
||||
Reference in New Issue
Block a user