/stripe-guides

How to block fraudulent customers in Stripe?

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.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to block fraudulent customers in Stripe?

Blocking Fraudulent Customers in Stripe: A Comprehensive Guide

 

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.

  1. Log in to your Stripe Dashboard (https://dashboard.stripe.com/).
  2. Navigate to Radar > Rules in the left sidebar.
  3. Enable Stripe Radar if not already activated.

// 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:

  1. In the Radar section, click on "Rules."
  2. Click "Add rule" to create a new rule.
  3. Define conditions based on risk factors like:
    • IP address location
    • Card country
    • Email domain
    • Purchase amount
    • Previous chargebacks

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.

  1. Go to Radar > Rules.
  2. Create a rule to limit the number of failed payment attempts:

// 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.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022