Add test button to launch stripe

This commit is contained in:
Owen
2026-02-04 14:13:25 -08:00
parent bf5dd3b0a1
commit 158d7b23d8
7 changed files with 216 additions and 16 deletions

View File

@@ -0,0 +1,37 @@
export enum LicenseId {
SMALL_LICENSE = "small_license",
BIG_LICENSE = "big_license"
}
export type LicensePriceSet = {
[key in LicenseId]: string;
};
export const licensePriceSet: LicensePriceSet = {
// Free license matches the freeLimitSet
[LicenseId.SMALL_LICENSE]: "price_1SxDwuDCpkOb237Bz0yTiOgN",
[LicenseId.BIG_LICENSE]: "price_1SxDy0DCpkOb237BWJxrxYkl"
};
export const licensePriceSetSandbox: LicensePriceSet = {
// Free license matches the freeLimitSet
// when matching license the keys closer to 0 index are matched first so list the licenses in descending order of value
[LicenseId.SMALL_LICENSE]: "price_1SxDwuDCpkOb237Bz0yTiOgN",
[LicenseId.BIG_LICENSE]: "price_1SxDy0DCpkOb237BWJxrxYkl"
};
export function getLicensePriceSet(
environment?: string,
sandbox_mode?: boolean
): LicensePriceSet {
if (
(process.env.ENVIRONMENT == "prod" &&
process.env.SANDBOX_MODE !== "true") ||
(environment === "prod" && sandbox_mode !== true)
) {
// THIS GETS LOADED CLIENT SIDE AND SERVER SIDE
return licensePriceSet;
} else {
return licensePriceSetSandbox;
}
}

View File

@@ -0,0 +1,113 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { customers, db } from "@server/db";
import { eq } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import config from "@server/lib/config";
import { fromError } from "zod-validation-error";
import stripe from "#private/lib/stripe";
import { getLicensePriceSet, LicenseId } from "@server/lib/billing/licenses";
const createCheckoutSessionParamsSchema = z.strictObject({
orgId: z.string(),
});
const createCheckoutSessionBodySchema = z.strictObject({
tier: z.enum([LicenseId.BIG_LICENSE, LicenseId.SMALL_LICENSE]),
});
export async function createCheckoutSessionoLicense(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = createCheckoutSessionParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId } = parsedParams.data;
const parsedBody = createCheckoutSessionBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { tier } = parsedBody.data;
// check if we already have a customer for this org
const [customer] = await db
.select()
.from(customers)
.where(eq(customers.orgId, orgId))
.limit(1);
// If we don't have a customer, create one
if (!customer) {
// error
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"No customer found for this organization"
)
);
}
const tierPrice = getLicensePriceSet()[tier]
const session = await stripe!.checkout.sessions.create({
client_reference_id: orgId, // So we can look it up the org later on the webhook
billing_address_collection: "required",
line_items: [
{
price: tierPrice, // Use the standard tier
quantity: 1
},
], // Start with the standard feature set that matches the free limits
customer: customer.customerId,
mode: "subscription",
success_url: `${config.getRawConfig().app.dashboard_url}/${orgId}/settings/license?success=true&session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${config.getRawConfig().app.dashboard_url}/${orgId}/settings/license?canceled=true`
});
return response<string>(res, {
data: session.url,
success: true,
error: false,
message: "Checkout session created successfully",
status: HttpCode.CREATED
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -29,7 +29,7 @@ const createCheckoutSessionSchema = z.strictObject({
orgId: z.string()
});
export async function createCheckoutSession(
export async function createCheckoutSessionSAAS(
req: Request,
res: Response,
next: NextFunction
@@ -87,7 +87,7 @@ export async function createCheckoutSession(
data: session.url,
success: true,
error: false,
message: "Organization created successfully",
message: "Checkout session created successfully",
status: HttpCode.CREATED
});
} catch (error) {

View File

@@ -11,8 +11,9 @@
* This file is not licensed under the AGPLv3.
*/
export * from "./createCheckoutSession";
export * from "./createCheckoutSessionSAAS";
export * from "./createPortalSession";
export * from "./getOrgSubscription";
export * from "./getOrgUsage";
export * from "./internalGetOrgTier";
export * from "./createCheckoutSessionLicense";

View File

@@ -159,11 +159,19 @@ if (build === "saas") {
);
authenticated.post(
"/org/:orgId/billing/create-checkout-session",
"/org/:orgId/billing/create-checkout-session-saas",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
logActionAudit(ActionsEnum.billing),
billing.createCheckoutSession
billing.createCheckoutSessionSAAS
);
authenticated.post(
"/org/:orgId/billing/create-checkout-session-license",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
logActionAudit(ActionsEnum.billing),
billing.createCheckoutSessionoLicense
);
authenticated.post(

View File

@@ -121,7 +121,7 @@ export default function GeneralPage() {
setIsLoading(true);
try {
const response = await api.post<AxiosResponse<string>>(
`/org/${org.org.orgId}/billing/create-checkout-session`,
`/org/${org.org.orgId}/billing/create-checkout-session-saas`,
{}
);
console.log("Checkout session response:", response.data);

View File

@@ -345,6 +345,37 @@ export default function GenerateLicenseKeyForm({
resetForm();
};
const handleTestCheckout = async () => {
setLoading(true);
try {
const response = await api.post<AxiosResponse<string>>(
`/org/${orgId}/billing/create-checkout-session-license`,
{
tier: "big_license"
}
);
console.log("Checkout session response:", response.data);
const checkoutUrl = response.data.data;
if (checkoutUrl) {
window.location.href = checkoutUrl;
} else {
toast({
title: "Failed to get checkout URL",
description: "Please try again later",
variant: "destructive"
});
setLoading(false);
}
} catch (error) {
toast({
title: "Checkout error",
description: formatAxiosError(error),
variant: "destructive"
});
setLoading(false);
}
};
return (
<Credenza open={open} onOpenChange={handleClose}>
<CredenzaContent className="max-w-4xl">
@@ -1066,16 +1097,26 @@ export default function GenerateLicenseKeyForm({
)}
{!generatedKey && useCaseType === "business" && (
<Button
type="submit"
form="generate-license-business-form"
disabled={loading}
loading={loading}
>
{t(
"generateLicenseKeyForm.buttons.generateLicenseKey"
)}
</Button>
<>
<Button
variant="secondary"
onClick={handleTestCheckout}
disabled={loading}
loading={loading}
>
TEST: Go to Checkout
</Button>
<Button
type="submit"
form="generate-license-business-form"
disabled={loading}
loading={loading}
>
{t(
"generateLicenseKeyForm.buttons.generateLicenseKey"
)}
</Button>
</>
)}
</CredenzaFooter>
</CredenzaContent>