Token导航 LogoToken导航TokenDH.com
前端设计敏感数据github未标认证来源可访问许可证需确认审计提醒

payment-security-clerk-billing-stripepayment 安全 clerk billing Stripe

Agent Skill

用于辅助安全审计、权限检查、凭据风险、认证流程和常见漏洞排查。它适合让 Agent 梳理敏感配置、检查依赖风险、分析鉴权逻辑或生成安全复核清单。使用时不能把工具输出直接当最终结论,涉及密钥、令牌、用户数据或生产系统时,应先确认最小权限、脱敏方式和操作边界。

总安装

3,192

周安装

133

GitHub Stars

7

下载量

1,064
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:payment-security-clerk-billing-stripe(payment 安全 clerk billing Stripe)
来源仓库:https://github.com/harperaa/secure-claude-skills
仓库路径:skills/payment-security-clerk-billing-stripe
安装命令:
npx skills add https://github.com/harperaa/secure-claude-skills --skill payment-security-clerk-billing-stripe
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/harperaa/secure-claude-skills --skill payment-security-clerk-billing-stripe

简介

payment-security-clerk-billing-stripe 用于辅助安全审计、权限检查和认证流程分析。

  • 适合梳理敏感配置、检查依赖风险或生成安全复核清单,尤其在支付相关场景中。
  • 不能将工具输出直接作为最终结论,涉及密钥或生产系统时应先确认最小权限和操作边界。
  • 通过 npx skills add 命令从指定 GitHub 仓库安装,需结合原始 README 核验具体用法。
  • 当前无原始 SKILL.md 内容可参考,实际功能以仓库内文档为准。

SKILL.md

Payment Security - Clerk Billing + Stripe

Why We Don't Handle Payments Directly

PCI-DSS Compliance Requirements

If you store, process, or transmit credit card data, you must comply with Payment Card Industry Data Security Standard (PCI-DSS). Requirements include:

  • Annual security audits ($20,000-$50,000)
  • Quarterly vulnerability scans
  • Secure network architecture
  • Encryption of cardholder data
  • Access control measures
  • Regular security testing

Small companies: 84% fail initial PCI audit

Ongoing compliance costs: $50,000-$200,000 annually

Real-World Payment Handling Failures

Target Breach (2013): 41 million card accounts compromised because they stored payment data and had insufficient security. Settlement: $18.5 million

Home Depot Breach (2014): 56 million cards stolen. They were storing card data locally. Settlement: $17.5 million

The Secure Approach: Never Touch Card Data

By using Clerk Billing + Stripe, we never see, store, or transmit credit card data. We're not subject to PCI-DSS. Stripe is.

Our Payment Architecture

What Happens (What DOESN'T Happen)

User subscribes:

  1. Frontend shows Clerk's PricingTable component
  2. User clicks subscribe → Clerk opens Stripe Checkout
  3. User enters card → Stripe's servers (not ours)
  4. Stripe processes payment → Stripe's servers (not ours)
  5. Stripe notifies Clerk → Webhook (verified by Clerk)
  6. Clerk updates subscription status
  7. Clerk notifies Convex → Webhook to our database
  8. Our app reads subscription status → Grants access

What Never Touches Our Servers

  • ❌ Credit card numbers
  • ❌ CVV codes
  • ❌ Expiration dates
  • ❌ Billing addresses (unless user separately provides)

What We Store

  • ✅ Subscription status (free/basic/pro)
  • ✅ Subscription start date
  • ✅ Customer ID (Stripe's internal ID, not card info)

This Architecture Means

  • We're NOT subject to PCI-DSS (Stripe is)
  • We can't leak card data (we never have it)
  • Stripe handles fraud detection
  • Stripe handles 3D Secure
  • Clerk handles webhook security

Implementation Files

  • components/custom-clerk-pricing.tsx - Pricing table component
  • app/dashboard/payment-gated/page.tsx - Example of subscription gating
  • convex/http.ts - Webhook receiver (signature verified by Svix)

Setting Up Clerk Billing

1. Configure in Clerk Dashboard

  1. Go to Clerk Dashboard → Billing
  2. Connect Stripe account
  3. Create subscription plans (Free, Basic, Pro)
  4. Copy Clerk Billing publishable key

2. Environment Variables

# .env.local

# Clerk Billing
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

# Stripe (automatically configured by Clerk Billing)
# No manual Stripe keys needed!

# Webhook signing secret (from Clerk)
CLERK_WEBHOOK_SECRET=whsec_...

3. Add Pricing Table Component

// components/custom-clerk-pricing.tsx
'use client';

import { PricingTable } from '@clerk/clerk-react';

export function CustomClerkPricing() {
  return (
    <div className="pricing-container">
      <h1>Choose Your Plan</h1>

      <PricingTable
        appearance={{
          elements: {
            card: 'border rounded-lg p-6',
            cardActive: 'border-blue-500',
            button: 'bg-blue-600 hover:bg-blue-700 text-white',
          }
        }}
      />
    </div>
  );
}

Checking Subscription Status

Server-Side (API Routes)

// app/api/premium-feature/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@clerk/nextjs/server';
import { handleUnauthorizedError, handleForbiddenError } from '@/lib/errorHandler';

export async function GET(request: NextRequest) {
  const { userId, sessionClaims } = await auth();

  if (!userId) {
    return handleUnauthorizedError();
  }

  // Check subscription status from Clerk
  const plan = sessionClaims?.metadata?.plan as string;

  if (plan === 'free_user') {
    return handleForbiddenError('Premium subscription required');
  }

  // User has paid subscription
  return NextResponse.json({
    message: 'Welcome to premium feature!',
    plan: plan
  });
}

Client-Side (Components)

'use client';

import { Protect } from '@clerk/nextjs';
import Link from 'next/link';

export function PremiumFeature() {
  return (
    <Protect
      condition={(has) => !has({ plan: "free_user" })}
      fallback={<UpgradePrompt />}
    >
      <div>
        {/* Premium feature content */}
        <h2>Premium Feature</h2>
        <p>This content is only visible to paid subscribers</p>
      </div>
    </Protect>
  );
}

function UpgradePrompt() {
  return (
    <div className="upgrade-prompt">
      <h3>Upgrade to Premium</h3>
      <p>This feature is available on our paid plans</p>
      <Link href="/pricing">
        <button>View Pricing</button>
      </Link>
    </div>
  );
}

Complete Payment-Gated Page Example

// app/dashboard/payment-gated/page.tsx
'use client';

import { Protect } from '@clerk/nextjs';
import { CustomClerkPricing } from '@/components/custom-clerk-pricing';

export default function PaymentGatedPage() {
  return (
    <div>
      <Protect
        condition={(has) => !has({ plan: "free_user" })}
        fallback={
          <div className="upgrade-required">
            <h1>Premium Access Required</h1>
            <p>Subscribe to access this page</p>
            <CustomClerkPricing />
          </div>
        }
      >
        <div className="premium-content">
          <h1>Premium Dashboard</h1>
          <p>Welcome to the premium features!</p>
          {/* Premium features here */}
        </div>
      </Protect>
    </div>
  );
}

Webhook Handling

Clerk Webhook (User & Subscription Events)

// app/api/webhooks/clerk/route.ts
import { Webhook } from 'svix';
import { headers } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;

  if (!WEBHOOK_SECRET) {
    throw new Error('Missing CLERK_WEBHOOK_SECRET');
  }

  // Get webhook headers
  const headerPayload = headers();
  const svix_id = headerPayload.get("svix-id");
  const svix_timestamp = headerPayload.get("svix-timestamp");
  const svix_signature = headerPayload.get("svix-signature");

  if (!svix_id || !svix_timestamp || !svix_signature) {
    return new Response('Missing svix headers', { status: 400 });
  }

  const payload = await request.json();
  const body = JSON.stringify(payload);

  // Verify webhook signature
  const wh = new Webhook(WEBHOOK_SECRET);
  let evt: any;

  try {
    evt = wh.verify(body, {
      "svix-id": svix_id,
      "svix-timestamp": svix_timestamp,
      "svix-signature": svix_signature,
    });
  } catch (err) {
    console.error('Webhook verification failed:', err);
    return new Response('Invalid signature', { status: 400 });
  }

  const { id, type, data } = evt;

  // Handle subscription events
  switch (type) {
    case 'subscription.created':
      await handleSubscriptionCreated(data);
      break;

    case 'subscription.updated':
      await handleSubscriptionUpdated(data);
      break;

    case 'subscription.deleted':
      await handleSubscriptionDeleted(data);
      break;

    case 'user.created':
      await handleUserCreated(data);
      break;

    case 'user.updated':
      await handleUserUpdated(data);
      break;
  }

  return new Response('', { status: 200 });
}

async function handleSubscriptionCreated(data: any) {
  const { user_id, plan, stripe_customer_id } = data;

  // Store subscription in database
  await db.subscriptions.create({
    userId: user_id,
    plan: plan,
    stripeCustomerId: stripe_customer_id,
    status: 'active',
    createdAt: Date.now()
  });

  // Update user metadata
  await db.users.update(
    { clerkId: user_id },
    { plan: plan, updatedAt: Date.now() }
  );
}

async function handleSubscriptionUpdated(data: any) {
  const { user_id, plan, status } = data;

  await db.subscriptions.update(
    { userId: user_id },
    {
      plan: plan,
      status: status,
      updatedAt: Date.now()
    }
  );

  // Update user plan
  await db.users.update(
    { clerkId: user_id },
    { plan: plan }
  );
}

async function handleSubscriptionDeleted(data: any) {
  const { user_id } = data;

  await db.subscriptions.update(
    { userId: user_id },
    {
      status: 'cancelled',
      cancelledAt: Date.now()
    }
  );

  // Downgrade to free
  await db.users.update(
    { clerkId: user_id },
    { plan: 'free_user' }
  );
}

Convex Webhook (Alternative)

If using Convex, you can receive Clerk webhooks directly:

// convex/http.ts
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
import { Webhook } from "svix";
import { internal } from "./_generated/api";

const http = httpRouter();

http.route({
  path: "/clerk-webhook",
  method: "POST",
  handler: httpAction(async (ctx, request) => {
    const payload = await request.text();
    const headers = request.headers;

    const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!);

    let evt: any;
    try {
      evt = wh.verify(payload, {
        "svix-id": headers.get("svix-id")!,
        "svix-timestamp": headers.get("svix-timestamp")!,
        "svix-signature": headers.get("svix-signature")!,
      });
    } catch (err) {
      return new Response("Invalid signature", { status: 400 });
    }

    const { type, data } = evt;

    switch (type) {
      case "subscription.created":
        await ctx.runMutation(internal.subscriptions.create, {
          userId: data.user_id,
          plan: data.plan,
          stripeCustomerId: data.stripe_customer_id,
        });
        break;

      case "subscription.updated":
        await ctx.runMutation(internal.subscriptions.update, {
          userId: data.user_id,
          plan: data.plan,
          status: data.status,
        });
        break;

      case "subscription.deleted":
        await ctx.runMutation(internal.subscriptions.cancel, {
          userId: data.user_id,
        });
        break;
    }

    return new Response("", { status: 200 });
  }),
});

export default http;

Testing Payments

Test Mode (Stripe Test Cards)

Always use Stripe test cards in development:

Success: 4242 4242 4242 4242
Decline: 4000 0000 0000 0002
3D Secure: 4000 0025 0000 3155
Insufficient funds: 4000 0000 0000 9995

CVV: Any 3 digits
Expiry: Any future date
ZIP: Any 5 digits

Testing Subscription Flow

  1. Go to /pricing
  2. Click "Subscribe" on a paid plan
  3. Clerk opens Stripe Checkout
  4. Enter test card: 4242 4242 4242 4242
  5. Complete checkout
  6. Verify subscription status updated
  7. Check premium features are accessible

Testing Webhook

# Use Stripe CLI to forward webhooks to local
stripe listen --forward-to localhost:3000/api/webhooks/clerk

# Trigger test subscription event
stripe trigger subscription.created

Handling Failed Payments

Failed Payment Flow

  1. Stripe attempts to charge card
  2. Payment fails (expired card, insufficient funds, etc.)
  3. Stripe notifies Clerk
  4. Clerk sends webhook: subscription.payment_failed
  5. We notify user via email
  6. Stripe retries (smart retry logic)
  7. If still fails after retries → subscription cancelled

Implementing Payment Failure Handling

// In webhook handler
case 'subscription.payment_failed':
  await handlePaymentFailed(data);
  break;

async function handlePaymentFailed(data: any) {
  const { user_id, attempt_count } = data;

  // Update subscription status
  await db.subscriptions.update(
    { userId: user_id },
    {
      status: 'past_due',
      lastPaymentFailed: Date.now(),
      failedAttempts: attempt_count
    }
  );

  // Send email to user
  await sendEmail({
    to: getUserEmail(user_id),
    subject: 'Payment Failed',
    template: 'payment-failed',
    data: {
      attemptCount: attempt_count,
      retryDate: calculateRetryDate(attempt_count)
    }
  });
}

Security Best Practices

1. Always Verify Webhooks

DON'T trust webhook data without verification:

// Bad - no signature verification
export async function POST(request: NextRequest) {
  const data = await request.json();
  // Process data directly - could be forged!
}

DO verify webhook signatures:

// Good - signature verified by Svix
const wh = new Webhook(WEBHOOK_SECRET);
const evt = wh.verify(body, headers); // Throws if invalid
// Now safe to process

2. Never Store Payment Info

DON'T store card data:

// Bad - PCI-DSS violation
await db.payments.create({
  userId,
  cardNumber: '4242424242424242', // ❌ NEVER DO THIS
  cvv: '123',                      // ❌ NEVER DO THIS
  expiry: '12/25'                  // ❌ NEVER DO THIS
});

DO store Stripe IDs only:

// Good - no card data
await db.subscriptions.create({
  userId,
  stripeCustomerId: 'cus_123',        // ✅ Stripe internal ID
  stripeSubscriptionId: 'sub_456',   // ✅ Stripe internal ID
  plan: 'pro',
  status: 'active'
});

3. Check Subscription Status on Server

DON'T rely on client-side checks:

// Bad - can be bypassed
'use client';
const { user } = useUser();
if (user?.publicMetadata?.plan === 'pro') {
  // Show premium feature - attacker can fake this
}

DO verify on server:

// Good - secure
export async function GET(request: NextRequest) {
  const { sessionClaims } = await auth();
  const plan = sessionClaims?.metadata?.plan;

  if (plan !== 'pro') {
    return handleForbiddenError();
  }

  // Premium feature access
}

4. Implement Idempotency

Handle duplicate webhooks (Stripe may retry):

// Track processed webhook IDs
const processedWebhooks = new Set<string>();

export async function POST(request: NextRequest) {
  const evt = await verifyWebhook(request);
  const { id } = evt;

  // Check if already processed
  if (processedWebhooks.has(id)) {
    return new Response('Already processed', { status: 200 });
  }

  // Process webhook
  await handleWebhook(evt);

  // Mark as processed
  processedWebhooks.add(id);

  return new Response('', { status: 200 });
}

What Clerk Billing Handles

Stripe API integration - No manual Stripe code needed ✅ Customer creation/management - Automatic ✅ Subscription lifecycle - Create, update, cancel ✅ Webhook signature verification - Built-in via Svix ✅ User/subscription sync - Automatic metadata updates ✅ Idempotency - Handles duplicate webhooks ✅ Retry logic - Smart retry on failed webhooks

What This Architecture Prevents

PCI-DSS compliance burden - Not subject to PCI-DSS ✅ Card data breaches - We never have card data ✅ Payment fraud - Stripe's fraud detection ✅ Webhook forgery - Svix signature verification ✅ Man-in-the-middle attacks - Stripe Checkout is HTTPS only ✅ Session hijacking - Clerk's secure session management

Common Mistakes to Avoid

DON'T try to process cards yourselfDON'T store any payment card informationDON'T trust webhook data without verificationDON'T rely on client-side subscription checks for access controlDON'T forget to handle failed paymentsDON'T expose Stripe secret keys in client code

DO use Clerk Billing + Stripe CheckoutDO verify webhook signatures (Svix)DO check subscription status on serverDO handle webhook events (created, updated, cancelled)DO test with Stripe test cards in developmentDO implement idempotency for webhooks

References

Next Steps

  • For subscription-based access control: Use auth-security skill with Protect component
  • For webhook endpoint security: Combine with rate-limiting skill
  • For error handling in payment processing: Use error-handling skill
  • For testing: Use security-testing skill

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

33.67%
按下载量换算358

Claude

32.47%
按下载量换算345

Cursor

19.36%
按下载量换算206

Gemini CLI

9.41%
按下载量换算100

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills