mirror of
https://github.com/formbricks/formbricks.git
synced 2026-03-06 18:39:28 -06:00
fix: harden cloud billing pricing table and webhook sync
This commit is contained in:
@@ -152,6 +152,8 @@ NOTION_OAUTH_CLIENT_SECRET=
|
||||
# Stripe Billing Variables
|
||||
STRIPE_SECRET_KEY=
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
CLOUD_STRIPE_PUBLISHABLE_KEY=
|
||||
CLOUD_STRIPE_PRICING_TABLE_ID=
|
||||
|
||||
# Oauth credentials for Google sheet integration
|
||||
GOOGLE_SHEETS_CLIENT_ID=
|
||||
@@ -230,4 +232,4 @@ REDIS_URL=redis://localhost:6379
|
||||
|
||||
|
||||
# Lingo.dev API key for translation generation
|
||||
LINGODOTDEV_API_KEY=your_api_key_here
|
||||
LINGODOTDEV_API_KEY=your_api_key_here
|
||||
|
||||
@@ -85,6 +85,8 @@ export const env = createEnv({
|
||||
SMTP_REJECT_UNAUTHORIZED_TLS: z.enum(["1", "0"]).optional(),
|
||||
STRIPE_SECRET_KEY: z.string().optional(),
|
||||
STRIPE_WEBHOOK_SECRET: z.string().optional(),
|
||||
CLOUD_STRIPE_PUBLISHABLE_KEY: z.string().optional(),
|
||||
CLOUD_STRIPE_PRICING_TABLE_ID: z.string().optional(),
|
||||
PUBLIC_URL: z
|
||||
.string()
|
||||
.url()
|
||||
@@ -202,6 +204,8 @@ export const env = createEnv({
|
||||
SMTP_AUTHENTICATED: process.env.SMTP_AUTHENTICATED,
|
||||
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
|
||||
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
|
||||
CLOUD_STRIPE_PUBLISHABLE_KEY: process.env.CLOUD_STRIPE_PUBLISHABLE_KEY,
|
||||
CLOUD_STRIPE_PRICING_TABLE_ID: process.env.CLOUD_STRIPE_PRICING_TABLE_ID,
|
||||
PUBLIC_URL: process.env.PUBLIC_URL,
|
||||
TURNSTILE_SECRET_KEY: process.env.TURNSTILE_SECRET_KEY,
|
||||
TURNSTILE_SITE_KEY: process.env.TURNSTILE_SITE_KEY,
|
||||
|
||||
@@ -46,8 +46,12 @@ export const webhookHandler = async (requestBody: string, stripeSignature: strin
|
||||
: null;
|
||||
const customerId =
|
||||
"customer" in eventObject && typeof eventObject.customer === "string" ? eventObject.customer : null;
|
||||
const clientReferenceId =
|
||||
"client_reference_id" in eventObject && typeof eventObject.client_reference_id === "string"
|
||||
? eventObject.client_reference_id
|
||||
: null;
|
||||
|
||||
let organizationId = metadataOrgId;
|
||||
let organizationId = metadataOrgId ?? clientReferenceId;
|
||||
if (!organizationId && customerId) {
|
||||
organizationId = await findOrganizationIdByStripeCustomerId(customerId);
|
||||
}
|
||||
@@ -57,6 +61,9 @@ export const webhookHandler = async (requestBody: string, stripeSignature: strin
|
||||
{ eventType: event.type, eventId: event.id },
|
||||
"Skipping Stripe webhook: organization not resolved"
|
||||
);
|
||||
if (event.type === "checkout.session.completed") {
|
||||
return { status: 500, message: "Checkout completed but organization could not be resolved." };
|
||||
}
|
||||
return { status: 200, message: { received: true } };
|
||||
}
|
||||
|
||||
@@ -71,6 +78,7 @@ export const webhookHandler = async (requestBody: string, stripeSignature: strin
|
||||
{ error, eventId: event.id, organizationId, eventType: event.type },
|
||||
"Failed to sync billing snapshot from Stripe webhook"
|
||||
);
|
||||
return { status: 500, message: "Stripe webhook processing failed; please retry." };
|
||||
}
|
||||
|
||||
return { status: 200, message: { received: true } };
|
||||
|
||||
@@ -15,9 +15,6 @@ import {
|
||||
} from "../actions";
|
||||
import { BillingSlider } from "./billing-slider";
|
||||
|
||||
const STRIPE_MONTHLY_PRICING_TABLE_ID = "prctbl_1T6ZLKCng0KywbKlSUAiFqH5";
|
||||
const STRIPE_MONTHLY_PRICING_PUBLISHABLE_KEY =
|
||||
"pk_test_51Sqt6uCng0KywbKlmnLtd8p2B1FfEAcM8O9IDiYdo1F2B6X7VYdMALhrpOU1vDB8SB3ikJshBeHz8Kj9iv89K6j3009S9mmY0h";
|
||||
const STRIPE_SUPPORTED_LOCALES = new Set([
|
||||
"bg",
|
||||
"cs",
|
||||
@@ -83,6 +80,8 @@ interface PricingTableProps {
|
||||
responseCount: number;
|
||||
projectCount: number;
|
||||
hasBillingRights: boolean;
|
||||
cloudStripePublishableKey: string | null;
|
||||
cloudStripePricingTableId: string | null;
|
||||
}
|
||||
|
||||
const getCurrentCloudPlan = (
|
||||
@@ -116,6 +115,8 @@ export const PricingTable = ({
|
||||
responseCount,
|
||||
projectCount,
|
||||
hasBillingRights,
|
||||
cloudStripePublishableKey,
|
||||
cloudStripePricingTableId,
|
||||
}: PricingTableProps) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const router = useRouter();
|
||||
@@ -264,14 +265,14 @@ export const PricingTable = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasBillingRights && (
|
||||
{hasBillingRights && cloudStripePublishableKey && cloudStripePricingTableId && (
|
||||
<div className="mb-12 w-full">
|
||||
<div className="w-full">
|
||||
<div className="mx-auto w-full max-w-[1200px]">
|
||||
<Script src="https://js.stripe.com/v3/pricing-table.js" strategy="afterInteractive" />
|
||||
{createElement("stripe-pricing-table", {
|
||||
"pricing-table-id": STRIPE_MONTHLY_PRICING_TABLE_ID,
|
||||
"publishable-key": STRIPE_MONTHLY_PRICING_PUBLISHABLE_KEY,
|
||||
"pricing-table-id": cloudStripePricingTableId,
|
||||
"publishable-key": cloudStripePublishableKey,
|
||||
...(stripeLocaleOverride
|
||||
? {
|
||||
"__locale-override": stripeLocaleOverride,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
|
||||
import { env } from "@/lib/env";
|
||||
import { getMonthlyOrganizationResponseCount } from "@/lib/organization/service";
|
||||
import { getOrganizationProjectsCount } from "@/lib/project/service";
|
||||
import { getTranslate } from "@/lingodotdev/server";
|
||||
@@ -50,6 +51,8 @@ export const PricingPage = async (props) => {
|
||||
responseCount={responseCount}
|
||||
projectCount={projectCount}
|
||||
hasBillingRights={hasBillingRights}
|
||||
cloudStripePublishableKey={env.CLOUD_STRIPE_PUBLISHABLE_KEY ?? null}
|
||||
cloudStripePricingTableId={env.CLOUD_STRIPE_PRICING_TABLE_ID ?? null}
|
||||
/>
|
||||
</PageContentWrapper>
|
||||
);
|
||||
|
||||
@@ -243,6 +243,8 @@
|
||||
"CLOUD_STRIPE_PRODUCT_ID_PRO",
|
||||
"CLOUD_STRIPE_PRODUCT_ID_SCALE",
|
||||
"CLOUD_STRIPE_PRODUCT_ID_TRIAL",
|
||||
"CLOUD_STRIPE_PUBLISHABLE_KEY",
|
||||
"CLOUD_STRIPE_PRICING_TABLE_ID",
|
||||
"SURVEYS_PACKAGE_MODE",
|
||||
"SURVEYS_PACKAGE_BUILD",
|
||||
"PUBLIC_URL",
|
||||
|
||||
Reference in New Issue
Block a user