关于 pricing-page
面向开发者的编码技能,可为现有应用搭建定价系统,涵盖套餐定义、功能权限、Dodo Payments 结账和定价界面。它会先检查项目,并避免替换不受支持的支付服务商。
主要功能
- 01检查应用技术栈和支付服务商
- 02定义套餐与额度包
- 03按套餐实施功能权限控制
- 04接入 Dodo Payments 结账和账单门户
- 05生成定价界面并验证流程
适用场景
- 01为现有应用添加付费套餐
- 02按套餐限制 API 路由和界面功能
- 03配置 Dodo Payments 结账与账单管理
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tushaarmehtaa/ tushar-skills --skill pricing-page安装后在 Claude Code 中输入/pricing-page确认已加载。
附带文件references/guide.md
详细介绍
搭建一套完整的定价系统——包括套餐定义、功能门控逻辑、Dodo Payments 结账流程和前端定价组件。先读取项目,再接入现有技术栈。
阶段 1:了解项目
开始编写前,先阅读代码库:
1.1 检测技术栈
- 框架:Next.js / 其他?
- 数据库:使用什么 ORM/客户端?用户表的结构是什么?
- 身份验证:API 路由如何识别当前用户?
- 现有支付集成:检查
package.json中的支付服务提供商:@dodopayments/sdk→ Dodo Payments(使用下方阶段 4)stripe→ 停止并报告:“不支持此服务提供商:此技能仅实现 Dodo Payments。保持 Stripe 不变,并使用针对 Stripe 的工作流。”@lemonsqueezy/lemonsqueezy.js→ 停止并报告:“不支持此服务提供商:此技能仅实现 Dodo Payments。保持 Lemon Squeezy 不变,并使用针对 Lemon Squeezy 的工作流。”- 无 → 确认用户是否要设置 Dodo Payments
切勿在已有不受支持的服务提供商时安装 Dodo。更改文件或依赖项前必须停止。
1.2 询问用户
I'll scaffold pricing for your [framework] app.
Quick decisions:
1. How many tiers? (e.g., Free + Pro, or Free + Pro + Enterprise)
2. What's the pricing model? (flat rate / credits / per-seat / usage-based)
3. Monthly billing, annual, or both?
4. What features are gated behind paid? (or let me suggest based on the codebase)
5. Confirm Dodo Payments? (This skill does not implement Stripe or Lemon Squeezy.)
阶段 2:套餐定义
创建一个单一的套餐事实来源。根据用户的回答进行调整:
// config/pricing.ts
export type Plan = 'free' | 'pro' | 'enterprise';
export const PLANS = {
free: {
name: 'Free',
price: 0,
description: 'Get started',
features: ['[Feature 1]', '[Feature 2]'],
limits: {
// Fill based on codebase — e.g., creditsPerMonth: 50
},
cta: 'Get Started',
ctaHref: '/signup',
},
pro: {
name: 'Pro',
priceMonthly: 0, // fill from user
priceAnnual: 0, // fill from user
description: 'For serious users',
features: ['Everything in Free', '[Pro Feature 1]', '[Pro Feature 2]'],
limits: {
// creditsPerMonth: 500, etc.
},
cta: 'Upgrade to Pro',
highlighted: true,
badge: 'Most Popular',
},
} as const;
对于基于额度的应用,在套餐定义旁添加额度包定义。
阶段 3:功能门控
创建一个实用工具,在运行任何受门控的功能之前检查访问权限。这是执行层——其他所有内容都只是展示:
// lib/feature-gate.ts
type Plan = 'free' | 'pro' | 'enterprise';
// Define which plans can access which features
// Populate based on what actually exists in the codebase
const FEATURE_ACCESS: Record<string, Plan[]> = {
'api-access': ['pro', 'enterprise'],
'export-data': ['pro', 'enterprise'],
'custom-domain': ['enterprise'],
'priority-support': ['enterprise'],
};
export function canAccess(userPlan: Plan, feature: string): boolean {
return FEATURE_ACCESS[feature]?.includes(userPlan) ?? false;
}
在 API 路由中,在执行任何高成本操作前进行检查:
export async function POST(req: Request) {
const user = await getAuthUser(req);
if (!canAccess(user.plan, 'api-access')) {
return Response.json(
{ error: 'This feature requires Pro.', upgradeUrl: '/pricing' },
{ status: 403 }
);
}
// ... rest of handler
}
在 UI 中,显示锁定状态,而不是隐藏功能。用户需要知道该功能存在:
function ExportButton({ userPlan }: { userPlan: Plan }) {
if (!canAccess(userPlan, 'export-data')) {
return (
<button
onClick={() => router.push('/pricing')}
className="opacity-60"
title="Upgrade to Pro to export"
>
🔒 Export — Pro only
</button>
);
}
return <button onClick={handleExport}>Export</button>;
}
阶段 4:集成 Dodo Payments
环境变量
DODO_API_KEY= # From Dodo dashboard
DODO_WEBHOOK_SECRET= # whsec_... format — see the dodo-webhook skill
DODO_PRODUCT_ID= # Product ID for Pro plan
APP_URL= # Frontend URL for checkout redirect
安装:npm install @dodopayments/sdk
创建结账端点
// app/api/payments/create-checkout/route.ts
import DodoPayments from '@dodopayments/sdk';
const dodo = new DodoPayments({ bearerToken: process.env.DODO_API_KEY });
export async function POST(req: Request) {
const user = await getAuthUser(req);
const { planId } = await req.json();
const checkout = await dodo.payments.create({
payment_link: true,
customer: { email: user.email },
product_cart: [{ product_id: process.env.DODO_PRODUCT_ID!, quantity: 1 }],
metadata: {
userId: user.id, // REQUIRED — webhook uses this
planId, // which plan they're buying
},
return_url: `${process.env.APP_URL}/checkout/success?plan=${planId}`,
});
return Response.json({ checkout_url: checkout.payment_link });
}
Webhook 通过元数据查找用户。 如果元数据中没有 userId,Webhook 就无法更新正确的账户。使用 dodo-webhook 技能接入 Webhook 处理程序。
客户门户
将用户链接到 Dodo 托管的账单门户,以便管理套餐、取消订阅和查看发票历史记录:
// app/api/billing/portal/route.ts
export async function GET(req: Request) {
const user = await getAuthUser(req);
const portal = await dodo.customerPortal.create({
customer_id: user.dodoCustomerId,
return_url: `${process.env.APP_URL}/settings/billing`,
});
return Response.json({ portal_url: portal.url });
}
在用户设置中添加一个“管理账单”链接,使其调用此端点。
结账成功页面
创建 /checkout/success——这是 Dodo 在付款后重定向到的页面。Webhook 可能会在重定向几秒后才到达,因此请轮询以获取更新后的套餐:
// app/checkout/success/page.tsx
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
export default function CheckoutSuccess() {
const [plan, setPlan] = useState<string | null>(null);
const router = useRouter();
useEffect(() => {
// Poll until plan updates (webhook may lag by a few seconds)
let attempts = 0;
const interval = setInterval(async () => {
const res = await fetch('/api/auth/me');
const user = await res.json();
if (user.plan !== 'free' || attempts > 10) {
setPlan(user.plan);
clearInterval(interval);
}
attempts++;
}, 1000);
return () => clearInterval(interval);
}, []);
if (!plan) return <p>Confirming your upgrade...</p>;
return (
<div>
<h1>You're on {plan}.</h1>
<p>Your account has been upgraded.</p>
<a href="/dashboard">Go to dashboard →</a>
</div>
);
}
阶段 5:定价 UI 组件
生成一个响应式定价组件。设计必须突出一个套餐——看到三个权重相同的套餐时,用户往往会在做出决定前离开:
// components/pricing-cards.tsx
'use client';
import { PLANS } from '@/config/pricing';
interface PricingCardsProps {
currentPlan?: string;
onUpgrade?: (planId: string) => void;
}
export function PricingCards({ currentPlan, onUpgrade }: PricingCardsProps) {
const handleUpgrade = async (planId: string) => {
const res = await fetch('/api/payments/create-checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ planId }),
});
const { checkout_url } = await res.json();
window.location.href = checkout_url;
};
return (
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
{Object.entries(PLANS).map(([planId, plan]) => (
<div
key={planId}
className={`rounded-xl border p-6 ${
'highlighted' in plan && plan.highlighted
? 'border-black shadow-xl'
: 'border-gray-200'
}`}
>
{'badge' in plan && plan.badge && (
<span className="text-xs font-bold uppercase tracking-widest text-black">
{plan.badge}
</span>
)}
<h3 className="mt-2 text-xl font-bold">{plan.name}</h3>
<p className="mt-1 text-3xl font-bold">
{'price' in plan ? (plan.price === 0 ? 'Free' : `$${plan.price}/mo`) : `$${plan.priceMonthly}/mo`}
</p>
<ul className="mt-4 space-y-2">
{plan.features.map((f) => (
<li key={f} className="flex items-center gap-2 text-sm text-gray-700">
<span className="text-green-500">✓</span> {f}
</li>
))}
</ul>
<div className="mt-6">
{currentPlan === planId ? (
<div className="py-2 text-center text-sm text-gray-400">Current plan</div>
) : (
<button
onClick={() => handleUpgrade(planId)}
className={`w-full rounded-lg py-2 text-sm font-medium ${
'highlighted' in plan && plan.highlighted
? 'bg-black text-white'
: 'border border-gray-300 text-gray-700 hover:border-black'
}`}
>
{plan.cta}
</button>
)}
</div>
</div>
))}
</div>
);
}
阶段 6:验证
Flow 1: Feature Gating
[ ] Free user hits gated endpoint → 403 with upgradeUrl
[ ] Pro user hits same endpoint → proceeds normally
[ ] Gated UI shows locked state, links to /pricing
Flow 2: Checkout
[ ] "Upgrade" button creates checkout session
[ ] Redirects to Dodo-hosted checkout page
[ ] userId is in checkout metadata
[ ] After payment, redirects to /checkout/success
Flow 3: Webhook (handled by the dodo-webhook skill)
[ ] Webhook verified and processed
[ ] User plan updated in database
[ ] Success page reflects new plan after polling
Flow 4: Billing Portal
[ ] "Manage billing" link accessible in settings
[ ] Opens Dodo customer portal
[ ] Returns to app after portal actions
Flow 5: Edge Cases
[ ] Users without dodoCustomerId don't crash portal link
[ ] Checkout success polling stops after plan updates
[ ] Env vars in .env.example, not hardcoded
有关定价心理学、A/B 测试思路和高级功能门控模式,请参阅 references/guide.md。