SkillAgentSearch skills...

RULE

Payment processing with Stripe, security, PCI compliance, and transaction handling

Install / Use

npx skills add henzard/Website

Installs into whichever agent you are using.

About this skill
📐

Cursor Rules

Cursor IDE rules (v2)

Quality Score

67/100

Category

Security

Supported Platforms

Cursor

Our assessment of RULE

RULE scores 67/100 on our quality scale, 1064th of 1,114 Security skills we index.

Its Cursor Rules is 18 KB long, well organised into 21 sections with 14 code examples: a thorough specification that gives an agent plenty to work with.

It has no GitHub stars yet, so there is no community track record; judge it on its content.

Substance
30/30
Structure
20/20
Description
12/15
Adoption
0/20
Freshness
5/15

Maintenance, license and trust

  • We could not determine when the repository was last updated.
  • Our last check on 2026-09-27 found the source still online.
  • No license is declared. By default that means all rights are reserved: you can read it, but reusing or redistributing it is not clearly permitted. Ask the author before building on it commercially.
  • Its trust signals score 68/100, with 3 cautions from licensing, adoption, age or documentation. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

Safety scan

No issues found

Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. An AI review of the same text found nothing harmful.

AI review by kimi-k2.7-code on 2026-09-25. Automated pattern scan on 2026-09-24. It catches known dangerous patterns, not every risk — read a skill before letting an agent act on it.

RULE compared with similar skills

All 4 of these similar skills score higher than RULE; compare them before choosing.

SkillScoreStarsUpdatedFormat
RULE (this skill)by henzard670—Cursor Rules
diagram-designby cathrynlavery10042.3k22d agoSKILL.md
protocol-reverse-engineeringby wshobson10039.9k20d agoSKILL.md
anndataby K-Dense-AI10046.4k20d agoSKILL.md
codeqlby github10039.3k17d agoSKILL.md

Frequently asked questions

How do I install RULE?
Run npx skills add henzard/Website. The install tabs above show the steps for each supported agent.
Which AI agents does RULE work with?
It is written for Cursor, as a Cursor Rules file. Other agents that read the same format can often use it too.
Is RULE safe to use?
Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. An AI review of the same text found nothing harmful. It declares no license and scores 68/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is RULE still maintained?
We could not determine when the repository was last updated.

description: "Payment processing with Stripe, security, PCI compliance, and transaction handling" alwaysApply: false

Payment Processing Standards

🎯 Core Principles

"Security first. Compliance always. Never store card data."

Payment rules:

  • ✅ Use certified payment processor (Stripe, PayPal)
  • ✅ Never handle raw card data
  • ✅ PCI DSS compliance through processor
  • ✅ Strong Customer Authentication (SCA)
  • ✅ Webhook signature verification
  • ✅ Idempotent operations

💳 Stripe Integration

Setup

npm install @stripe/stripe-js @stripe/react-stripe-js stripe

Firebase Cloud Function

// functions/src/payment.ts

import { onCall, HttpsError } from 'firebase-functions/v2/https';
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2023-10-16',
});

/**
 * Create payment intent
 */
export const createPaymentIntent = onCall(async (request) => {
  // Authentication check
  if (!request.auth) {
    throw new HttpsError('unauthenticated', 'User must be authenticated');
  }

  const { amount, currency, metadata } = request.data;

  // Validation
  if (!amount || amount <= 0) {
    throw new HttpsError('invalid-argument', 'Invalid amount');
  }

  if (!currency) {
    throw new HttpsError('invalid-argument', 'Currency is required');
  }

  try {
    // Get customer from Firestore or create new
    const customerId = await getOrCreateStripeCustomer(request.auth.uid);

    // Create payment intent
    const paymentIntent = await stripe.paymentIntents.create({
      amount: Math.round(amount * 100), // Convert to cents
      currency: currency.toLowerCase(),
      customer: customerId,
      metadata: {
        userId: request.auth.uid,
        ...metadata,
      },
      // Enable automatic payment methods
      automatic_payment_methods: {
        enabled: true,
      },
    });

    // Log transaction
    await admin.firestore().collection('transactions').add({
      userId: request.auth.uid,
      paymentIntentId: paymentIntent.id,
      amount,
      currency,
      status: 'pending',
      createdAt: admin.firestore.FieldValue.serverTimestamp(),
    });

    return {
      clientSecret: paym…[redacted],
      paymentIntentId: paymentIntent.id,
    };
  } catch (error: any) {
    console.error('Payment intent creation failed:', error);
    throw new HttpsError('internal', error.message);
  }
});

/**
 * Get or create Stripe customer
 */
async function getOrCreateStripeCustomer(userId: string): Promise<string> {
  const userDoc = await admin.firestore().collection('users').doc(userId).get();
  const userData = userDoc.data();

  // Return existing customer ID
  if (userData?.stripeCustomerId) {
    return userData.stripeCustomerId;
  }

  // Create new customer
  const customer = await stripe.customers.create({
    email: userData?.email,
    metadata: {
      firebaseUID: userId,
    },
  });

  // Store customer ID
  await userDoc.ref.update({
    stripeCustomerId: customer.id,
  });

  return customer.id;
}

Frontend Payment Form

// components/PaymentForm.tsx

import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';

const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY);

interface PaymentFormProps {
  amount: number;
  currency: string;
  onSuccess: (paymentIntentId: string) => void;
  onError: (error: string) => void;
}

function PaymentFormContent({ amount, onSuccess, onError }: PaymentFormProps) {
  const stripe = useStripe();
  const elements = useElements();
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    if (!stripe || !elements) {
      return;
    }

    setLoading(true);

    try {
      // Confirm payment
      const { error, paymentIntent } = await stripe.confirmPayment({
        elements,
        confirmParams: {
          return_url: `${window.location.origin}/payment/success`,
        },
        redirect: 'if_required',
      });

      if (error) {
        onError(error.message || 'Payment failed');
      } else if (paymentIntent && paymentIntent.status === 'succeeded') {
        onSuccess(paymentIntent.id);
      }
    } catch (err: any) {
      onError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <label className="block text-sm font-medium mb-2">
          Payment Details
        </label>
        <PaymentElement />
      </div>

      <div className="bg-gray-50 p-4 rounded">
        <div className="flex justify-between items-center">
          <span className="font-medium">Total:</span>
          <span className="text-2xl font-bold">
            ${amount.toFixed(2)}
          </span>
        </div>
      </div>

      <button
        type="submit"
        disabled={!stripe || loading}
        className="w-full bg-primary-600 text-white py-3 rounded-lg hover:bg-primary-700 disabled:opacity-50"
      >
        {loading ? 'Processing...' : `Pay $${amount.toFixed(2)}`}
      </button>

      <p className="text-xs text-gray-500 text-center">
        🔒 Secured by Stripe. We never see your card details.
      </p>
    </form>
  );
}

export const PaymentForm: React.FC<PaymentFormProps> = (props) => {
  const [clientSecret, setClientSecret] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    // Create payment intent on mount
    const createIntent = async () => {
      try {
        const result = await functions.httpsCallable('createPaymentIntent')({
          amount: props.amount,
          currency: props.currency,
          metadata: {
            // Add relevant metadata
          },
        });

        setClientSecret(result.data.clientSecret);
      } catch (err: any) {
        setError(err.message);
      }
    };

    createIntent();
  }, [props.amount, props.currency]);

  if (error) {
    return <ErrorMessage message={error} />;
  }

  if (!clientSecret) {
    return <LoadingSpinner />;
  }

  return (
    <Elements stripe={stripePromise} options={{ clientSecret }}>
      <PaymentFormContent {...props} />
    </Elements>
  );
};

🔔 Webhook Handling

Stripe Webhook Endpoint

// functions/src/webhooks.ts

import { onRequest } from 'firebase-functions/v2/https';
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2023-10-16',
});

const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;

export const stripeWebhook = onRequest(async (req, res) => {
  const sig = req.headers['stripe-signature'] as string;

  let event: Stripe.Event;

  try {
    // Verify webhook signature
    event = stripe.webhooks.constructEvent(req.rawBody, sig, webhookSecret);
  } catch (err: any) {
    console.error('Webhook signature verification failed:', err.message);
    res.status(400).send(`Webhook Error: ${err.message}`);
    return;
  }

  // Handle event
  try {
    switch (event.type) {
      case 'payment_intent.succeeded':
        await handlePaymentSucceeded(event.data.object as Stripe.PaymentIntent);
        break;

      case 'payment_intent.payment_failed':
        await handlePaymentFailed(event.data.object as Stripe.PaymentIntent);
        break;

      case 'charge.refunded':
        await handleChargeRefunded(event.data.object as Stripe.Charge);
        break;

      case 'customer.subscription.created':
        await handleSubscriptionCreated(event.data.object as Stripe.Subscription);
        break;

      case 'customer.subscription.deleted':
        await handleSubscriptionCancelled(event.data.object as Stripe.Subscription);
        break;

      default:
        console.log(`Unhandled event type: ${event.type}`);
    }

    res.json({ received: true });
  } catch (err: any) {
    console.error('Webhook handler failed:', err);
    res.status(500).send('Webhook handler failed');
  }
});

async function handlePaymentSucceeded(paymentIntent: Stripe.PaymentIntent) {
  const userId = paymentIntent.metadata.userId;

  // Update transaction status
  await admin
    .firestore()
    .collection('transactions')
    .where('paymentIntentId', '==', paymentIntent.id)
    .get()
    .then((snapshot) => {
      snapshot.docs.forEach((doc) => {
        doc.ref.update({
          status: 'succeeded',
          paidAt: admin.firestore.FieldValue.serverTimestamp(),
        });
      });
    });

  // Fulfill order
  await fulfillOrder(userId, paymentIntent.id);

  // Send confirmation email
  await sendPaymentConfirmationEmail(userId, paymentIntent.id);

  // Log for analytics
  await admin.firestore().collection('analytics_events').add({
    event: 'payment_succeeded',
    userId,
    amount: paymentIntent.amount / 100,
    currency: paymentIntent.currency,
    timestamp: admin.firestore.FieldValue.serverTimestamp(),
  });
}

async function handlePaymentFailed(paymentIntent: Stripe.PaymentIntent) {
  const userId = paymentIntent.metadata.userId;

  // Update transaction status
  await admin
    .firestore()
    .collection('transactions')
    .where('paymentIntentId', '==', paymentIntent.id)
    .get()
    .then((snapshot) => {
      snapshot.docs.forEach((doc) => {
        doc.ref.update({
          status: 'failed',
          failureReason: paymentIntent.last_payment_error?.message,
        });
      });
    });

  // Notify user
  await sendPaymentFailedEmail(userId, paymentIntent.id);
}

💰 Pricing & Currency

Price Formatting

// utils/currency.ts

export function formatCurrency(
  amount: number,
  currency: string = 'USD',
  locale: string = 'en-US'
): string {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: currency.toUpperCase(),
  }).format(amount);
}

// Usage
formatCurrency(1999, 'USD'); // "$1,999.00"
formatCurrency(1999, 'EUR', 'de-DE'); // "1.999,00 €"

Tax Calculation

// services/taxService.ts

interface TaxCalculation {
  subtotal: number;
  tax: number;
  total: number;
}

export async function calculateTax(
  amount: number,
  country: string,
  region?: string
): Promise<TaxCalculation> {
  // Use Stripe Tax or TaxJar API
  const taxRate = await getTaxRate(country, region);

  const subtotal = amount;
  const tax = subtotal * taxRate;
  const total = subtotal + tax;

  return { subtotal, tax, total };
}

async function getTaxRate(country: string, region?: string): Promise<number> {
  // Simplified - use actual tax service in production
  const taxRates: Record<string, number> = {
    US_CA: 0.0725, // California
    US_NY: 0.04, // New York
    GB: 0.20, // UK VAT
    DE: 0.19, // Germany VAT
  };

  const key = region ? `${country}_${region}` : country;
  return taxRates[key] || 0;
}

📜 Invoice Generation

// services/invoiceService.ts

export async function generateInvoice(transactionId: string): Promise<string> {
  const transaction = await getTransaction(transactionId);
  const user = await getUser(transaction.userId);

  const invoiceData = {
    invoiceNumber: `INV-${Date.now()}`,
    date: new Date().toISOString(),
    customer: {
      name: user.displayName,
      email: user.email,
      address: user.billingAddress,
    },
    items: transaction.items.map((item: any) => ({
      description: item.name,
      quantity: item.quantity,
      price: item.price,
      total: item.quantity * item.price,
    })),
    subtotal: transaction.subtotal,
    tax: transaction.tax,
    total: transaction.total,
    paymentMethod: 'Card ending in ****' + transaction.last4,
  };

  // Generate PDF (using jsPDF or similar)
  const pdfUrl = await generatePDF(invoiceData);

  // Store invoice
  await admin.firestore().collection('invoices').add({
    ...invoiceData,
    pdfU

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars0
CategorySecurity
UpdatedNaNy ago
Forks0

Trust signals

68/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

2 medium1 low