mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-07 19:47:26 +02:00
🐛 Fix licensing checkout and webhook (#1731)
This commit is contained in:
parent
518b66aa9a
commit
3ae7f7e021
7 changed files with 30 additions and 31 deletions
|
@ -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({
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.");
|
||||||
|
|
|
@ -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<
|
||||||
|
|
|
@ -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";
|
|
@ -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[]
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue