Learn how to block fraudulent customers in Stripe with step-by-step methods, custom rules, API tips, and advanced fraud prevention strategies for secure payments.
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
Introduction
Fraudulent activities in payment processing can significantly impact your business. Stripe offers several methods to identify and block suspicious customers. This tutorial provides detailed steps to implement fraud prevention measures in your Stripe account.
Step 1: Set Up Radar for Fraud Detection
Stripe Radar is a built-in fraud prevention system that uses machine learning to identify and block fraudulent transactions.
// No code required for this step - this is done through the Stripe Dashboard interface
Step 2: Create Custom Radar Rules
Custom rules allow you to block specific patterns associated with fraud:
Example rule in the Stripe Dashboard:
// Example rule syntax in Stripe Radar
IF (:card_country != :ip_country) AND (:risk\_score > 80) THEN BLOCK
Step 3: Implement Card Testing Prevention
Card testing is a common fraud technique where attackers test stolen card numbers with small amounts.
// Example rule to block after multiple failed attempts
IF (:number_payment_attempts > 3) AND (:payment\_status = "failed") THEN BLOCK
Step 4: Block Specific Customers Using API
You can programmatically block specific customers using Stripe's API:
// Using Node.js with Stripe API
const stripe = require('stripe')('sk_test_your_secret_key');
// Block a specific customer
async function blockCustomer(customerId) {
try {
const customer = await stripe.customers.update(
customerId,
{
metadata: { blocked: 'true' }
}
);
console.log(`Customer ${customerId} has been marked as blocked`);
return customer;
} catch (error) {
console.error(`Error blocking customer: ${error.message}`);
throw error;
}
}
// Example usage
blockCustomer('cus\_1234567890');
Step 5: Check Customer Status Before Processing Payments
Before processing payments, check if a customer has been blocked:
// Node.js example
const stripe = require('stripe')('sk_test_your_secret_key');
async function isCustomerBlocked(customerId) {
try {
const customer = await stripe.customers.retrieve(customerId);
return customer.metadata.blocked === 'true';
} catch (error) {
console.error(`Error checking customer status: ${error.message}`);
throw error;
}
}
async function processPayment(customerId, amountInCents, currency) {
// First check if the customer is blocked
const blocked = await isCustomerBlocked(customerId);
if (blocked) {
throw new Error('This customer has been blocked due to suspicious activity');
}
// If not blocked, proceed with payment
const paymentIntent = await stripe.paymentIntents.create({
amount: amountInCents,
currency: currency,
customer: customerId,
confirm: true,
payment_method_types: ['card'],
});
return paymentIntent;
}
Step 6: Implement a Blocklist Database
For more sophisticated blocking, create a database to store information about blocked customers:
// Example using Node.js, Express, and MongoDB
const express = require('express');
const mongoose = require('mongoose');
const stripe = require('stripe')('sk_test_your_secret_key');
// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/fraud\_prevention', { useNewUrlParser: true });
// Create a schema for blocked entities
const BlockedEntitySchema = new mongoose.Schema({
type: { type: String, enum: ['email', 'ip', 'card_fingerprint', 'customer_id'] },
value: { type: String, required: true },
reason: String,
blockedAt: { type: Date, default: Date.now }
});
const BlockedEntity = mongoose.model('BlockedEntity', BlockedEntitySchema);
// Express middleware to check if a request should be blocked
async function checkBlocklist(req, res, next) {
const email = req.body.email;
const ip = req.ip;
// Check if email or IP is blocked
const blocked = await BlockedEntity.findOne({
$or: [
{ type: 'email', value: email },
{ type: 'ip', value: ip }
]
});
if (blocked) {
return res.status(403).json({
error: 'Access denied due to suspicious activity'
});
}
next();
}
// API endpoint to add an entity to blocklist
app.post('/api/block', async (req, res) => {
try {
const { type, value, reason } = req.body;
const newBlockedEntity = new BlockedEntity({
type,
value,
reason
});
await newBlockedEntity.save();
res.json({ success: true, message: 'Entity has been blocked' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Use the middleware in your payment routes
app.post('/api/payment', checkBlocklist, async (req, res) => {
// Process payment if not blocked
// ...
});
Step 7: Set Up Webhook Notifications for Fraud Alerts
Configure Stripe webhooks to notify you of potential fraud in real-time:
// Node.js webhook handler
const express = require('express');
const app = express();
// This is your Stripe CLI webhook secret for testing
const endpointSecret = 'whsec_your_webhook\_secret';
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
} catch (err) {
console.log(`Webhook Error: ${err.message}`);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle specific events
switch (event.type) {
case 'radar.early_fraud_warning.created':
const fraudWarning = event.data.object;
console.log('Fraud warning detected!', fraudWarning);
// Automatically block the customer
blockCustomer(fraudWarning.charge.customer);
// Add to your custom blocklist
addToBlocklist('customer\_id', fraudWarning.charge.customer, 'Radar fraud warning');
break;
case 'charge.disputed':
const dispute = event.data.object;
console.log('Dispute received!', dispute);
// Consider blocking customer after disputes
if (dispute.reason === 'fraudulent') {
blockCustomer(dispute.customer);
}
break;
}
res.json({received: true});
});
// Function to add to blocklist
async function addToBlocklist(type, value, reason) {
const newBlockedEntity = new BlockedEntity({
type,
value,
reason
});
await newBlockedEntity.save();
console.log(`Added ${type}:${value} to blocklist for reason: ${reason}`);
}
app.listen(3000, () => console.log('Webhook server running on port 3000'));
Step 8: Implement 3D Secure Authentication
For high-risk transactions, implement 3D Secure to add an extra layer of security:
// Front-end implementation with Stripe.js
// HTML
// JavaScript
const stripe = Stripe('pk_test_your_publishable_key');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const { paymentMethod, error } = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
});
if (error) {
const errorElement = document.getElementById('error-message');
errorElement.textContent = error.message;
return;
}
// Send to your server
const response = await fetch('/create-payment-intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
paymentMethodId: paymentMethod.id,
amount: 1000, // $10.00
}),
});
const result = await response.json();
// Handle 3D Secure authentication if required
if (result.requiresAction) {
const { error, paymentIntent } = await stripe.handleCardAction(
result.clientSecret
);
if (error) {
// Show error to customer
const errorElement = document.getElementById('error-message');
errorElement.textContent = error.message;
} else {
// 3D Secure authentication was successful
// Confirm with your server
const confirmResponse = await fetch('/confirm-payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
paymentIntentId: paymentIntent.id,
}),
});
const confirmResult = await confirmResponse.json();
if (confirmResult.success) {
// Payment complete - show success message
console.log('Payment successful!');
}
}
} else if (result.success) {
// Payment successful
console.log('Payment successful!');
}
});
// Server-side implementation
app.post('/create-payment-intent', async (req, res) => {
const { paymentMethodId, amount } = req.body;
try {
// Check if customer should be blocked first
// ...your blocking logic here...
const paymentIntent = await stripe.paymentIntents.create({
amount: amount,
currency: 'usd',
payment\_method: paymentMethodId,
confirmation\_method: 'manual',
confirm: true,
// Always require 3D Secure for high-risk transactions
payment_method_options: {
card: {
request_three_d\_secure: 'any'
}
},
return\_url: 'https://your-website.com/payment-complete',
});
if (paymentIntent.status === 'requires\_action') {
// 3D Secure is required
res.json({
requiresAction: true,
clientSecret: paymentIntent.client\_secret
});
} else if (paymentIntent.status === 'succeeded') {
// Payment successful
res.json({ success: true });
}
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.post('/confirm-payment', async (req, res) => {
const { paymentIntentId } = req.body;
try {
const paymentIntent = await stripe.paymentIntents.confirm(paymentIntentId);
if (paymentIntent.status === 'succeeded') {
res.json({ success: true });
} else {
res.json({
success: false,
status: paymentIntent.status
});
}
} catch (error) {
res.status(400).json({ error: error.message });
}
});
Step 9: Analyze Payment Patterns for Manual Review
Create a system to flag suspicious transactions for manual review:
// Example algorithm to detect suspicious patterns
async function analyzeTransaction(transaction) {
let riskScore = 0;
const riskFactors = [];
// Check transaction amount - high amounts may be risky
if (transaction.amount > 1000) {
riskScore += 10;
riskFactors.push('High transaction amount');
}
// Check if customer is new
if (transaction.isNewCustomer) {
riskScore += 5;
riskFactors.push('New customer');
}
// Check shipping/billing address mismatch
if (transaction.shipping_address !== transaction.billing_address) {
riskScore += 15;
riskFactors.push('Shipping/billing address mismatch');
}
// Check unusual purchase time
const hour = new Date(transaction.created).getHours();
if (hour >= 1 && hour <= 5) {
riskScore += 5;
riskFactors.push('Unusual purchase time (1-5 AM)');
}
// Check if multiple different cards used
const cardCount = await getCardCountForCustomer(transaction.customer\_id);
if (cardCount > 3) {
riskScore += 20;
riskFactors.push('Multiple payment methods');
}
// Check velocity - multiple purchases in short time
const recentTransactions = await getRecentTransactions(
transaction.customer\_id,
24 // hours
);
if (recentTransactions.length > 5) {
riskScore += 25;
riskFactors.push('High transaction velocity');
}
// Flag for manual review if risk score is high
if (riskScore >= 30) {
await flagForReview(transaction.id, riskScore, riskFactors);
return {
isRisky: true,
score: riskScore,
factors: riskFactors
};
}
return {
isRisky: false,
score: riskScore,
factors: riskFactors
};
}
// Store transactions flagged for review
async function flagForReview(transactionId, riskScore, riskFactors) {
const flaggedTransaction = new FlaggedTransaction({
transaction\_id: transactionId,
risk\_score: riskScore,
risk\_factors: riskFactors,
reviewed: false,
flagged\_at: new Date()
});
await flaggedTransaction.save();
// Optionally send notification
sendAlertEmail(
'Suspicious transaction detected',
`Transaction ${transactionId} has been flagged with risk score ${riskScore}.\nRisk factors: ${riskFactors.join(', ')}`
);
}
Step 10: Implement Advanced Verification for High-Risk Transactions
For transactions deemed high-risk, implement additional verification steps:
// Request additional verification for high-risk transactions
async function requestAdditionalVerification(customerId, transactionId) {
// Generate a unique verification code
const verificationCode = generateRandomCode(6);
// Store the verification request
const verification = new VerificationRequest({
customer\_id: customerId,
transaction\_id: transactionId,
code: verificationCode,
expires\_at: new Date(Date.now() + 3600000), // 1 hour expiry
verified: false
});
await verification.save();
// Send verification code to customer
// This could be via email, SMS, etc.
await sendVerificationEmail(
customer.email,
'Verify your recent transaction',
`Please use this code to verify your transaction: ${verificationCode}`
);
return verification;
}
// Verify the code submitted by customer
app.post('/api/verify-transaction', async (req, res) => {
const { transactionId, code } = req.body;
try {
const verification = await VerificationRequest.findOne({
transaction\_id: transactionId,
code: code,
expires\_at: { $gt: new Date() }
});
if (!verification) {
return res.status(400).json({
success: false,
message: 'Invalid or expired verification code'
});
}
// Mark as verified
verification.verified = true;
await verification.save();
// Complete the transaction
const paymentIntent = await stripe.paymentIntents.confirm(transactionId);
return res.json({
success: true,
status: paymentIntent.status
});
} catch (error) {
return res.status(500).json({
success: false,
error: error.message
});
}
});
Conclusion
By implementing these steps, you can significantly reduce fraudulent activities in your Stripe account. Remember to regularly review and update your fraud prevention measures as new fraud patterns emerge. Balancing security with customer experience is key - too strict rules might block legitimate customers, while too lenient rules might allow fraud to occur.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.