RULE
Payment processing with Stripe, security, PCI compliance, and transaction handling
Install / Use
npx skills add henzard/WebsiteInstalls into whichever agent you are using.
Cursor Rules
Cursor IDE rules (v2)
Quality Score
Category
SecuritySupported Platforms
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.
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 foundOur 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.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| RULE (this skill)by henzard | 67 | 0 | — | Cursor Rules |
| diagram-designby cathrynlavery | 100 | 42.3k | 22d ago | SKILL.md |
| protocol-reverse-engineeringby wshobson | 100 | 39.9k | 20d ago | SKILL.md |
| anndataby K-Dense-AI | 100 | 46.4k | 20d ago | SKILL.md |
| codeqlby github | 100 | 39.3k | 17d ago | SKILL.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.
Skill content
View source on GitHubdescription: "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
diagram-design
42.3kCreate branded architecture, IT current-state, flowchart, sequence, state machine, ER/data model, timeline, swimlane, quadrant, radar/spider, polar chart (polar/radial lollipop), loop/flywheel, nested, tree, org chart, layer stack, Venn, pyramid/funnel, treemap, heatmap, bar, waterfall, line, Gantt…
protocol-reverse-engineering
39.9kMaster network protocol reverse engineering including packet analysis, protocol dissection, and custom protocol documentation
anndata
46.4kHandles annotated matrices in single-cell analysis, .h5ad and Zarr files, and integration with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
codeql
39.3kComprehensive guide for setting up and configuring CodeQL code scanning via GitHub Actions workflows and the CodeQL CLI. This skill should be used when users need help with code scanning configuration, CodeQL workflow files, CodeQL CLI commands, SARIF output, security analysis setup, or troubleshoot…
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
