🐛 Fix licensing checkout and webhook (#1731)

This commit is contained in:
Luke Vella 2025-05-26 19:07:17 +01:00 committed by GitHub
parent 518b66aa9a
commit 3ae7f7e021
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 30 additions and 31 deletions

View file

@ -46,15 +46,8 @@ if (env.LICENSE_API_AUTH_TOKEN) {
}), }),
), ),
async (c) => { async (c) => {
const { const { type, seats, expiresAt, licenseeEmail, licenseeName, version } =
type, c.req.valid("json");
seats,
expiresAt,
licenseeEmail,
licenseeName,
version,
stripeCustomerId,
} = c.req.valid("json");
try { try {
const license = await prisma.license.create({ const license = await prisma.license.create({
@ -67,7 +60,6 @@ if (env.LICENSE_API_AUTH_TOKEN) {
expiresAt, expiresAt,
licenseeEmail, licenseeEmail,
licenseeName, licenseeName,
stripeCustomerId,
}, },
}); });
return c.json({ return c.json({

View file

@ -63,6 +63,7 @@ export async function GET(request: NextRequest) {
success_url: "https://rallly.co/licensing/thank-you", success_url: "https://rallly.co/licensing/thank-you",
metadata: { metadata: {
licenseType: type, licenseType: type,
version: 4,
seats, seats,
} satisfies LicenseCheckoutMetadata, } satisfies LicenseCheckoutMetadata,
}); });

View file

@ -1,3 +1,4 @@
import { env } from "@/env";
import { licensingClient } from "@/features/licensing/client"; import { licensingClient } from "@/features/licensing/client";
import { licenseCheckoutMetadataSchema } from "@/features/licensing/schema"; import { licenseCheckoutMetadataSchema } from "@/features/licensing/schema";
import { subscriptionCheckoutMetadataSchema } from "@/features/subscription/schema"; import { subscriptionCheckoutMetadataSchema } from "@/features/subscription/schema";
@ -38,10 +39,11 @@ async function handleSelfHostedCheckoutSessionCompleted(
if (!success) { if (!success) {
// If there is no metadata than this is likely a donation from a payment link // If there is no metadata than this is likely a donation from a payment link
console.info("No metadata found for session: ", checkoutSession.id);
return; return;
} }
const { licenseType, seats } = data; const { licenseType, version, seats } = data;
const customerDetails = checkoutSession.customer_details; const customerDetails = checkoutSession.customer_details;
@ -63,13 +65,13 @@ async function handleSelfHostedCheckoutSessionCompleted(
type: licenseType, type: licenseType,
licenseeEmail: email, licenseeEmail: email,
licenseeName: customerDetails.name ?? undefined, licenseeName: customerDetails.name ?? undefined,
version,
seats, seats,
stripeCustomerId: checkoutSession.customer as string,
}); });
if (!license || !license.data) { if (!license || !license.data) {
throw new Error( throw new Error(
`Failed to create team license for session: ${checkoutSession.id} - ${license?.error}`, `Failed to create license for session: ${checkoutSession.id} - ${license?.error}`,
); );
} }
@ -79,7 +81,7 @@ async function handleSelfHostedCheckoutSessionCompleted(
to: email, to: email,
from: { from: {
name: "Luke from Rallly", name: "Luke from Rallly",
address: process.env.SUPPORT_EMAIL, address: env.SUPPORT_EMAIL,
}, },
props: { props: {
licenseKey: license.data.key, licenseKey: license.data.key,

View file

@ -10,7 +10,7 @@ export class LicensingClient {
authToken?: string; authToken?: string;
constructor({ constructor({
apiUrl = "https://licensing.rallly.co", apiUrl = "https://licensing.rallly.co/api/licensing/v1",
authToken, authToken,
}: { }: {
apiUrl?: string; apiUrl?: string;
@ -24,7 +24,7 @@ export class LicensingClient {
throw new Error("Licensing API auth token is not configured."); throw new Error("Licensing API auth token is not configured.");
} }
const res = await fetch(`${this.apiUrl}/api/v1/licenses`, { const res = await fetch(`${this.apiUrl}/licenses`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -39,16 +39,13 @@ export class LicensingClient {
return createLicenseResponseSchema.parse(await res.json()); return createLicenseResponseSchema.parse(await res.json());
} }
async validateLicenseKey(input: ValidateLicenseInputKeySchema) { async validateLicenseKey(input: ValidateLicenseInputKeySchema) {
const res = await fetch( const res = await fetch(`${this.apiUrl}/licenses/actions/validate-key`, {
`${this.apiUrl}/api/v1/licenses/actions/validate-key`, method: "POST",
{ headers: {
method: "POST", "Content-Type": "application/json",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
}, },
); body: JSON.stringify(input),
});
if (!res.ok) { if (!res.ok) {
throw new Error("Failed to validate license key."); throw new Error("Failed to validate license key.");

View file

@ -31,12 +31,11 @@ export type ApiResponse<T> = {
export const createLicenseInputSchema = z.object({ export const createLicenseInputSchema = z.object({
type: licenseTypeSchema, type: licenseTypeSchema,
seats: z.number().optional(), seats: z.coerce.number().optional(),
expiresAt: z.date().optional(), expiresAt: z.coerce.date().optional(),
licenseeEmail: z.string().optional(), licenseeEmail: z.string().optional(),
licenseeName: z.string().optional(), licenseeName: z.string().optional(),
version: z.number().optional(), version: z.coerce.number().optional(),
stripeCustomerId: z.string().optional(),
}); });
export type CreateLicenseInput = z.infer<typeof createLicenseInputSchema>; export type CreateLicenseInput = z.infer<typeof createLicenseInputSchema>;
@ -80,7 +79,8 @@ export type ValidateLicenseKeyResponse = z.infer<
export const licenseCheckoutMetadataSchema = z.object({ export const licenseCheckoutMetadataSchema = z.object({
licenseType: licenseTypeSchema, licenseType: licenseTypeSchema,
seats: z.number(), version: z.coerce.number(),
seats: z.coerce.number(),
}); });
export type LicenseCheckoutMetadata = z.infer< export type LicenseCheckoutMetadata = z.infer<

View file

@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `stripe_customer_id` on the `licenses` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "licenses" DROP COLUMN "stripe_customer_id";

View file

@ -20,7 +20,6 @@ model License {
licenseeEmail String? @map("licensee_email") licenseeEmail String? @map("licensee_email")
licenseeName String? @map("licensee_name") licenseeName String? @map("licensee_name")
status LicenseStatus @default(ACTIVE) @map("status") status LicenseStatus @default(ACTIVE) @map("status")
stripeCustomerId String? @map("stripe_customer_id")
validations LicenseValidation[] validations LicenseValidation[]