Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2acd3e8
feat: add RevenueCat integration templates and modular scaffolding su…
Alisha-21-cloud Jun 17, 2026
820a395
Merge branch 'main' into feat/add-revenuecat-payments
Alisha-21-cloud Jun 17, 2026
5924633
chore:fix prompt resolver registry and tech resource link configuration
Alisha-21-cloud Jun 17, 2026
169ac9b
Merge branch 'feat/add-revenuecat-payments' of https://github.com/Ali…
Alisha-21-cloud Jun 17, 2026
7c05711
chore : fixed syntax error
Alisha-21-cloud Jun 18, 2026
40e9e04
Merge branch 'main' into feat/add-revenuecat-payments
Alisha-21-cloud Jun 18, 2026
1846cc3
Merge branch 'main' into feat/add-revenuecat-payments
Marve10s Jun 25, 2026
6518fff
Merge branch 'main' into feat/add-revenuecat-payments
Marve10s Jun 30, 2026
79cd94a
feat: add RevenueCat integration templates and modular scaffolding su…
Alisha-21-cloud Jun 17, 2026
a3dc495
chore:fix prompt resolver registry and tech resource link configuration
Alisha-21-cloud Jun 17, 2026
e87287c
chore : fixed syntax error
Alisha-21-cloud Jun 18, 2026
d10405d
Merge branch 'feat/add-revenuecat-payments' of https://github.com/Ali…
Alisha-21-cloud Jul 1, 2026
6edc130
Merge branch 'main' into feat/add-revenuecat-payments
Marve10s Jul 3, 2026
2471b6f
Merge branch 'main' into feat/add-revenuecat-payments
Marve10s Jul 4, 2026
9571c80
Merge remote-tracking branch 'fork-alisha/feat/add-revenuecat-payment…
Marve10s Jul 4, 2026
00a242f
fix(revenuecat): make native-bare components type-check and tighten g…
Marve10s Jul 4, 2026
ffb21cb
chore(web): reduce constant.ts diff to just the RevenueCat entry
Marve10s Jul 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/cli/src/helpers/addons/mcp-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ function getRecommendedMcpServers(config: ProjectConfig): McpServerDef[] {
});
}

if (config.payments === "revenuecat") {
servers.push({
key: "revenuecat",
label: "RevenueCat",
name: "revenuecat",
target: "https://mcp.revenuecat.ai/mcp",
});
}

return servers;
}

Expand Down
26 changes: 26 additions & 0 deletions apps/cli/src/helpers/core/post-installation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ export async function displayPostInstallInstructions(
);
const polarInstructions =
config.payments === "polar" ? getPolarInstructions(backend, packageManager) : "";
const revenueCatInstructions =
config.payments === "revenuecat" ? getRevenueCatInstructions(backend, packageManager) : "";
const paymentSetupInstructions = getPaymentSetupInstructions(config.payments, backend);
const alchemyDeployInstructions = getAlchemyDeployInstructions(
runCmd,
Expand Down Expand Up @@ -283,6 +285,7 @@ export async function displayPostInstallInstructions(
if (authSetupInstructions) output += `\n${authSetupInstructions.trim()}\n`;
if (betterAuthConvexInstructions) output += `\n${betterAuthConvexInstructions.trim()}\n`;
if (polarInstructions) output += `\n${polarInstructions.trim()}\n`;
if (revenueCatInstructions) output += `\n${revenueCatInstructions.trim()}\n`;
if (paymentSetupInstructions) output += `\n${paymentSetupInstructions.trim()}\n`;

if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
Expand Down Expand Up @@ -690,6 +693,29 @@ function getPolarInstructions(backend: Backend, packageManager: string) {
return `${pc.bold("Polar Payments Setup:")}\n${pc.cyan("•")} Get access token & product ID from ${pc.underline("https://sandbox.polar.sh/")}\n${pc.cyan("•")} Set POLAR_ACCESS_TOKEN in ${envPath}`;
}

function getRevenueCatInstructions(backend: Backend, packageManager: string) {
const base =
`${pc.bold("RevenueCat Payments Setup:")}\n` +
`${pc.cyan("•")} Create a project, entitlement, and offering in ${pc.underline("https://app.revenuecat.com/")}\n` +
`${pc.cyan("•")} Set the public SDK keys in ${pc.white("apps/native/.env")}:\n` +
`${pc.white(" EXPO_PUBLIC_REVENUECAT_IOS_KEY=appl_your_ios_key")}\n` +
`${pc.white(" EXPO_PUBLIC_REVENUECAT_ANDROID_KEY=goog_your_android_key")}\n` +
`${pc.white(" EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID=pro")}`;

if (backend === "convex") {
const cmd = packageManager === "npm" ? "npx" : packageManager;
return (
`${base}\n` +
`${pc.cyan("•")} Set the webhook secret (min 32 chars) from ${pc.white("packages/backend")}:\n` +
`${pc.white(" cd packages/backend")}\n` +
`${pc.white(` ${cmd} convex env set REVENUECAT_WEBHOOK_AUTH your_webhook_secret`)}\n` +
`${pc.cyan("•")} Configure a RevenueCat webhook to ${pc.white("https://<your-convex-site-url>/webhooks/revenuecat")} using the same value as the Authorization header`
);
}

return base;
}

function getAlchemyDeployInstructions(
runCmd: string,
webDeploy: WebDeploy,
Expand Down
18 changes: 18 additions & 0 deletions apps/cli/src/prompts/payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ export function resolvePaymentsPrompt(
(context.auth === "better-auth" || context.auth === "better-auth-organizations") &&
(context.frontends?.length === 0 || splitFrontends(context.frontends).web.length > 0);

const hasNativeFrontend = (context.frontends ?? []).some(
(frontend) =>
frontend === "native-bare" ||
frontend === "native-uniwind" ||
frontend === "native-unistyles",
);

const isRevenueCatCompatible = hasNativeFrontend;

const options: Array<{ value: Payments; label: string; hint: string }> = [];

if (isPolarCompatible) {
Expand Down Expand Up @@ -76,6 +85,15 @@ export function resolvePaymentsPrompt(
},
);

if (isRevenueCatCompatible) {
options.push({
value: "revenuecat" as Payments,
label: "RevenueCat",
hint: "In-app subscriptions and cross-platform monetization for mobile.",
});
}


return {
shouldPrompt: true,
mode: "single",
Expand Down
5 changes: 4 additions & 1 deletion apps/cli/src/prompts/prompt-resolver-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,10 @@ export const PROMPT_RESOLVER_REGISTRY: ResolverRegistry = {
payments: {
schemaValues: PAYMENTS_VALUES,
resolve: resolvePaymentsPrompt,
coverageContexts: [{ auth: "better-auth", backend: "hono", frontends: ["next"] }],
coverageContexts: [
{ auth: "better-auth", backend: "hono", frontends: ["next"] },
{ auth: "better-auth", backend: "hono", frontends: ["native-bare"] },
],
},
realtime: {
schemaValues: REALTIME_VALUES,
Expand Down
9 changes: 9 additions & 0 deletions apps/cli/src/utils/compatibility-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,15 @@ export function validatePaymentsCompatibility(
);
}
}

if (payments === "revenuecat") {
const { native } = splitFrontends(frontends);
if (native.length === 0) {
exitWithError(
"RevenueCat payments requires a native frontend. Please select a native frontend or choose a different payments provider.",
);
}
}
}

export function validateExamplesCompatibility(
Expand Down
48 changes: 48 additions & 0 deletions apps/cli/test/payments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,54 @@ describe("Payments Options", () => {
});
});

describe("RevenueCat payments", () => {
test("revenuecat with native-bare frontend", async () => {
const result = await runTRPCTest(
createCustomConfig({
projectName: "revenuecat-native-bare",
frontend: ["native-bare"],
backend: "hono",
api: "none",
payments: "revenuecat",
}),
);
expectSuccess(result);
});

test("revenuecat with native-unistyles frontend", async () => {
const result = await runTRPCTest(
createCustomConfig({
projectName: "revenuecat-native-unistyles",
frontend: ["native-unistyles"],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
api: "none",
payments: "revenuecat",
}),
);
expectSuccess(result);
});

test("revenuecat with native-uniwind and better-auth (convex combined webhook)", async () => {
const result = await runTRPCTest(
createCustomConfig({
projectName: "revenuecat-uniwind-better-auth",
frontend: ["native-uniwind"],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
api: "none",
auth: "better-auth",
payments: "revenuecat",
}),
);
expectSuccess(result);
});
});

describe("No payments option", () => {
test("none payments option", async () => {
const result = await runTRPCTest(
Expand Down
8 changes: 8 additions & 0 deletions apps/web/src/lib/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,14 @@ export const TECH_OPTIONS: Record<
color: "from-indigo-500 to-blue-600",
default: false,
},
{
id: "revenuecat",
name: "RevenueCat",
description: "In-app subscriptions and cross-platform monetization for mobile.",
icon: "https://cdn.simpleicons.org/revenuecat/F2545B",
color: "from-red-400 to-red-600",
default: false,
},
{
id: "none",
name: "No Payments",
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/lib/tech-resource-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ const BASE_LINKS: LinkMap = {
githubUrl: "https://github.com/PaddleHQ/paddle-node-sdk",
},
dodo: { docsUrl: "https://docs.dodopayments.com/" },
revenuecat: { docsUrl: "https://www.revenuecat.com/docs" },
resend: {
docsUrl: "https://resend.com/docs",
githubUrl: "https://github.com/resend/resend-node",
Expand Down
40 changes: 40 additions & 0 deletions packages/template-generator/src/processors/env-vars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ function buildNativeVars(
frontend: string[],
backend: ProjectConfig["backend"],
auth: ProjectConfig["auth"],
payments: ProjectConfig["payments"],
mobilePush: ProjectConfig["mobilePush"],
mobileDeepLinking: ProjectConfig["mobileDeepLinking"],
): EnvVariable[] {
Expand Down Expand Up @@ -574,6 +575,26 @@ function buildNativeVars(
});
}

if (payments === "revenuecat") {
vars.push(
{
key: "EXPO_PUBLIC_REVENUECAT_IOS_KEY",
value: "",
condition: true,
},
{
key: "EXPO_PUBLIC_REVENUECAT_ANDROID_KEY",
value: "",
condition: true,
},
{
key: "EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID",
value: "pro",
condition: true,
},
);
}

if (mobilePush === "expo-notifications") {
vars.push({
key: "EXPO_PUBLIC_EAS_PROJECT_ID",
Expand Down Expand Up @@ -677,6 +698,15 @@ function buildConvexBackendVars(
);
}

if (payments === "revenuecat") {
vars.push({
key: "REVENUECAT_WEBHOOK_AUTH",
value: "",
condition: true,
comment: "Shared secret for RevenueCat webhook authentication (min 32 characters)",
});
}

return vars;
}

Expand Down Expand Up @@ -720,6 +750,15 @@ ${hasWeb ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}`;
# Create a Polar webhook at https://<your-convex-site-url>/polar/events
# Enable: product.created, product.updated, subscription.created, subscription.updated

`;
}

if (payments === "revenuecat") {
commentBlocks += `# Set RevenueCat environment variables
# npx convex env set REVENUECAT_WEBHOOK_AUTH your_webhook_secret_min_32_chars
# Create a RevenueCat webhook at https://<your-convex-site-url>/webhooks/revenuecat
# Set the webhook Authorization header to the same REVENUECAT_WEBHOOK_AUTH value

`;
}

Expand Down Expand Up @@ -1916,6 +1955,7 @@ export function processEnvVariables(vfs: VirtualFileSystem, config: ProjectConfi
frontend,
backend,
auth,
payments,
config.mobilePush,
config.mobileDeepLinking,
);
Expand Down
23 changes: 23 additions & 0 deletions packages/template-generator/src/processors/payments-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,27 @@ export function processPaymentsDeps(vfs: VirtualFileSystem, config: ProjectConfi
}
}
}

if (payments === "revenuecat") {
const nativePath = "apps/native/package.json";
const hasNativeFrontend = frontend.some((f) =>
["native-bare", "native-uniwind", "native-unistyles"].includes(f),
);

if (hasNativeFrontend && vfs.exists(nativePath)) {
addPackageDependency({
vfs,
packagePath: nativePath,
dependencies: ["react-native-purchases"],
});
}

if (backend === "convex" && vfs.exists(backendPath)) {
addPackageDependency({
vfs,
packagePath: backendPath,
dependencies: ["convex-revenuecat"],
});
}
}
}
40 changes: 38 additions & 2 deletions packages/template-generator/src/template-handlers/payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ export async function processPaymentsTemplates(
const hasSvelteWeb = config.frontend.includes("svelte");
const hasSolidWeb = config.frontend.includes("solid");

const nativeVariant = config.frontend.includes("native-bare")
? "bare"
: config.frontend.includes("native-uniwind")
? "uniwind"
: config.frontend.includes("native-unistyles")
? "unistyles"
: null;

if (config.backend === "convex") {
processTemplatesFromPrefix(
vfs,
Expand All @@ -26,6 +34,16 @@ export async function processPaymentsTemplates(
"packages/backend",
config,
);

if (config.payments === "revenuecat" && config.auth !== "better-auth") {
processTemplatesFromPrefix(
vfs,
templates,
"payments/revenuecat/convex/no-better-auth",
"packages/backend",
config,
);
}
} else if (config.backend !== "none") {
processTemplatesFromPrefix(
vfs,
Expand All @@ -36,12 +54,30 @@ export async function processPaymentsTemplates(
);
}

if (nativeVariant) {
processTemplatesFromPrefix(
vfs,
templates,
`payments/${config.payments}/native/base`,
"apps/native",
config,
);
processTemplatesFromPrefix(
vfs,
templates,
`payments/${config.payments}/native/${nativeVariant}`,
"apps/native",
config,
);
}


if (hasReactWeb) {
const reactFramework = config.frontend.includes("react-vite")
? "react-router"
: config.frontend.find((f) =>
["tanstack-router", "react-router", "tanstack-start", "next", "vinext"].includes(f),
);
["tanstack-router", "react-router", "tanstack-start", "next", "vinext"].includes(f),
);
if (reactFramework) {
processTemplatesFromPrefix(
vfs,
Expand Down
4 changes: 4 additions & 0 deletions packages/template-generator/src/utils/add-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,10 @@ export const dependencyVersionMap = {
dodopayments: "^2.40.1",
"dodopayments-checkout": "^1.9.4",

// Payments - RevenueCat
"react-native-purchases": "^9.10.5",
"convex-revenuecat": "^0.3.2",

// File Upload - UploadThing
uploadthing: "^7.7.4",
"@uploadthing/react": "^7.3.3",
Expand Down
Loading
Loading