# Payment Fraud Prevention

### The Problem

Payment fraud costs businesses over $40 billion annually. Traditional defenses focus on validating the card itself — CVV checks, AVS matching, 3D Secure — but they miss a critical dimension: **who is using the card**.

A fraudster with stolen card details can:

* Pass all card verification checks (they have the full card data)
* Use a different email address each time
* Ship to new addresses or use package forwarding
* Clear cookies and use incognito mode
* Rotate through VPNs and proxies

{% hint style="danger" %}
**The result:** The same fraudster hits your checkout repeatedly with different stolen cards, and each transaction looks like a unique, legitimate customer.
{% endhint %}

#### Common Payment Fraud Patterns

| Fraud Type            | Description                                              | Business Impact                  |
| --------------------- | -------------------------------------------------------- | -------------------------------- |
| **Card Testing**      | Bots test thousands of stolen cards with small purchases | Chargebacks, processor penalties |
| **Stolen Card Fraud** | Using compromised card details for purchases             | Direct loss + chargeback fees    |
| **Friendly Fraud**    | Legitimate purchases disputed as "unauthorized"          | Revenue loss, increased disputes |
| **Account Takeover**  | Accessing accounts to use saved payment methods          | Customer trust damage            |
| **Reseller Fraud**    | Bulk purchases with stolen cards for resale              | Inventory loss, chargebacks      |
| **Refund Abuse**      | Claiming items not received or damaged                   | Direct financial loss            |

#### Why Traditional Fraud Detection Fails

<div data-with-frame="true"><figure><img src="/files/WvBbQRmwUweVu9vcIMzx" alt=""><figcaption></figcaption></figure></div>

1. Fraudster uses stolen card #1 → Blocked by bank
2. Same fraudster tries card #2 with new email → Approved (looks like a new customer)
3. Same fraudster tries card #3 with VPN → Approved (different IP address)
4. Same fraudster tries card #4 in incognito → Approved (no cookies to track)

{% hint style="danger" %}
The problem: Each attempt looks like a different person. Your system can't connect them.
{% endhint %}

***

### The Solution: Device-Linked Payment Intelligence

Guardian Stack adds a persistent identity layer to every transaction. Even when fraudsters change cards, emails, addresses, and IP addresses, they're still using the same physical device — and Guardian detects it.

#### How It Works

<div data-with-frame="true"><figure><img src="/files/wEPwN0A44MS3wNKktVtZ" alt=""><figcaption></figcaption></figure></div>

1. Customer reaches checkout
2. Guardian SDK silently collects device signals
3. Customer submits payment
4. Your backend fetches the Guardian event and checks:
   * Has this device had chargebacks before?
   * Is this device testing multiple cards?
   * Does the location match the billing address?
   * Is this a bot or automated browser?
5. Approve, review, or decline the transaction

{% hint style="info" %}
**Key insight:** The `visitorId` persists across sessions, browsers, and cleared cookies. A fraudster who caused chargebacks last month is instantly recognizable today.
{% endhint %}

***

### Implementation Guide

#### Step 1: Frontend — Capture Device Signals at Checkout

Install the Guardian JS SDK:

```bash
npm install @guardianstack/guardian-js
```

Initialize Guardian when your app loads, then call `.get()` at checkout:

```typescript
import { loadAgent } from "@guardianstack/guardian-js";

// Initialize once when your app starts
const guardian = await loadAgent({
  siteKey: "YOUR_SITE_KEY",
});

async function handleCheckout(orderData: OrderData) {
  // 1. Get Guardian signals before processing payment
  const response = await guardian.get();
  const requestId = response?.requestId;

  // 2. Submit order with Guardian requestId
  const result = await fetch("/api/checkout", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      ...orderData,
      guardianRequestId: requestId,
    }),
  });

  return result.json();
}
```

#### Step 2: Backend — Assess Risk Before Charging

Install the Guardian Server SDK:

```bash
npm install @guardianstack/guardianjs-server
```

Create your checkout endpoint with fraud checks:

```typescript
import {
  createGuardianClient,
  isBot,
  isVPN,
  isTampering,
  isIncognito,
  isVirtualized,
} from "@guardianstack/guardianjs-server";

const guardianClient = createGuardianClient({
  secret: process.env.GUARDIAN_SECRET_KEY!,
});

app.post("/api/checkout", async (req, res) => {
  const { guardianRequestId, cardToken, amount, shippingAddress } = req.body;

  // 1. Fetch the Guardian event
  const event = await guardianClient.getEvent(guardianRequestId);

  // 2. Extract key identifiers
  const visitorId = event.identification.visitorId;
  const ipAddress = event.identification.ip;
  const deviceLocation = event.identification.location;

  // 3. Check for automation (card testing bots)
  if (isBot(event)) {
    await logFraudAttempt("bot_detected", { visitorId, amount });
    return res.status(403).json({
      error: "Transaction declined",
      code: "SECURITY_CHECK_FAILED",
    });
  }

  // 4. Check for browser tampering (anti-detect browsers)
  if (isTampering(event)) {
    await logFraudAttempt("tampering_detected", { visitorId, amount });
    return res.status(403).json({
      error: "Transaction declined",
      code: "SECURITY_CHECK_FAILED",
    });
  }

  // 5. Check if this device has previous chargebacks
  const previousChargebacks = await db.chargebacks.count({
    where: { visitorId },
  });

  if (previousChargebacks > 0) {
    await logFraudAttempt("previous_chargeback", { visitorId, amount });
    return res.status(403).json({
      error: "Transaction declined",
      code: "PAYMENT_NOT_ACCEPTED",
    });
  }

  // 6. Check velocity (card testing detection)
  const velocity = event.velocity;
  if (velocity["5m"] > 3 || velocity["1h"] > 10) {
    await logFraudAttempt("high_velocity", { visitorId, velocity, amount });
    return res.status(429).json({
      error: "Too many payment attempts",
      code: "RATE_LIMITED",
    });
  }

  // 7. Check for geographic anomalies
  const ipCountry = deviceLocation?.country_code;
  const cardCountry = await getCardCountry(cardToken); // From your payment processor

  if (ipCountry && cardCountry && ipCountry !== cardCountry && isVPN(event)) {
    // VPN + country mismatch = high risk
    await flagForManualReview(req.body, event, "geo_mismatch_vpn");
    // Optionally still process but flag for review
  }

  // 8. All checks passed — process payment
  const paymentResult = await processPayment(cardToken, amount);

  if (paymentResult.success) {
    // Store visitorId with order for future fraud correlation
    await db.orders.create({
      data: {
        orderId: paymentResult.orderId,
        visitorId,
        ipAddress,
        amount,
        deviceCountry: ipCountry,
      },
    });
  }

  return res.json(paymentResult);
});
```

***

### Real-World Examples

#### Stolen Card Detection

**Scenario:** A fraudster obtains stolen card details and attempts purchases on your site.

```typescript
app.post("/api/checkout", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Check how many DIFFERENT cards this device has used
  const uniqueCards = await db.orders.findMany({
    where: { visitorId },
    select: { cardLastFour: true, cardBrand: true },
    distinct: ["cardLastFour", "cardBrand"],
  });

  // Multiple cards from same device = card testing
  if (uniqueCards.length >= 3) {
    await logFraudAttempt("multiple_cards_same_device", {
      visitorId,
      cardCount: uniqueCards.length,
    });

    return res.status(403).json({
      error: "Transaction declined",
      message: "Please contact support",
    });
  }

  // Check if any previous orders from this device were charged back
  const chargedBackOrders = await db.orders.findMany({
    where: {
      visitorId,
      status: "chargeback",
    },
  });

  if (chargedBackOrders.length > 0) {
    return res.status(403).json({
      error: "Transaction declined",
      code: "STOLEN_CARD_DETECTED",
    });
  }

  // Process payment...
});
```

#### Card Testing Prevention

**Scenario:** Bots test thousands of stolen cards with small purchases to find valid ones.

```typescript
app.post("/api/checkout", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.guardianRequestId);
  const visitorId = event.identification.visitorId;
  const velocity = event.velocity;

  // High velocity = card testing
  if (velocity["5m"] > 5) {
    // Block and alert
    await alertSecurityTeam("card_testing_detected", {
      visitorId,
      attemptsIn5Min: velocity["5m"],
      ipAddress: event.identification.ip,
    });

    return res.status(429).json({
      error: "Too many attempts",
      retryAfter: 3600, // 1 hour
    });
  }

  // Check for bot indicators
  if (isBot(event) || isVirtualized(event)) {
    return res.status(403).json({
      error: "Transaction declined",
    });
  }

  // Check decline rate for this device
  const recentAttempts = await db.paymentAttempts.findMany({
    where: {
      visitorId,
      createdAt: { gte: subHours(new Date(), 1) },
    },
  });

  const declinedCount = recentAttempts.filter((a) => a.status === "declined").length;
  const declineRate = recentAttempts.length > 0 
    ? declinedCount / recentAttempts.length 
    : 0;

  if (declineRate > 0.5 && recentAttempts.length >= 3) {
    return res.status(403).json({
      error: "Transaction declined",
      code: "HIGH_DECLINE_RATE",
    });
  }

  // Process payment...
});
```

#### Returning Fraudster Detection

**Scenario:** A fraudster who caused chargebacks 3 months ago returns with a new email, card, and VPN.

```typescript
app.post("/api/checkout", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Check against fraud blacklist (populated from chargebacks)
  const isBlacklisted = await db.fraudBlacklist.findFirst({
    where: { visitorId },
  });

  if (isBlacklisted) {
    // Don't reveal why — just decline
    await logFraudAttempt("blacklisted_device_returned", {
      visitorId,
      originalIncident: isBlacklisted.reason,
      daysSinceBlacklist: daysBetween(isBlacklisted.createdAt, new Date()),
    });

    return res.status(403).json({
      error: "Transaction declined",
      code: "PAYMENT_NOT_ACCEPTED",
    });
  }

  // Check if this device is linked to ANY account with chargebacks
  const linkedAccounts = await db.accounts.findMany({
    where: { visitorId },
    include: { orders: { where: { status: "chargeback" } } },
  });

  const hasChargebackHistory = linkedAccounts.some(
    (account) => account.orders.length > 0
  );

  if (hasChargebackHistory) {
    return res.status(403).json({
      error: "Transaction declined",
    });
  }

  // Process payment...
});
```

#### Geographic Anomaly Detection

**Scenario:** Card billing address is in New York, but the device is connecting from Eastern Europe via VPN.

```typescript
app.post("/api/checkout", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.guardianRequestId);
  const { billingAddress, shippingAddress } = req.body;

  const deviceLocation = event.identification.location;
  const ipInfo = event.ipInfo;

  // Detect VPN with timezone mismatch
  const vpnData = event.vpn;
  if (vpnData?.detected && vpnData?.timezoneDifference > 3) {
    // VPN detected AND significant timezone mismatch
    await flagForManualReview(req.body, event, "vpn_timezone_mismatch");
  }

  // Check if IP is from datacenter (not residential)
  if (ipInfo?.is_datacenter && !ipInfo?.is_mobile) {
    // Datacenter IP + not mobile = likely proxy/VPN
    await flagForManualReview(req.body, event, "datacenter_ip");
  }

  // Compare device country with billing country
  const deviceCountry = deviceLocation?.country_code;
  const billingCountry = billingAddress?.country;

  if (deviceCountry && billingCountry && deviceCountry !== billingCountry) {
    // Country mismatch — increase scrutiny
    if (isVPN(event) || isIncognito(event)) {
      // High risk: country mismatch + privacy tools
      return res.status(403).json({
        error: "Transaction declined",
        code: "VERIFICATION_REQUIRED",
      });
    }

    // Medium risk: just flag for review
    await flagForManualReview(req.body, event, "country_mismatch");
  }

  // Process payment...
});
```

***

### Handling Chargebacks: Close the Loop

When you receive a chargeback, update your fraud database to catch the same fraudster next time:

```typescript
// Webhook handler for chargeback notifications
app.post("/webhooks/chargeback", async (req, res) => {
  const { orderId, reason, amount } = req.body;

  // Get the original order with device info
  const order = await db.orders.findUnique({
    where: { id: orderId },
  });

  if (!order?.visitorId) {
    return res.json({ received: true });
  }

  // Update order status
  await db.orders.update({
    where: { id: orderId },
    data: { status: "chargeback" },
  });

  // Add device to fraud blacklist
  await db.fraudBlacklist.upsert({
    where: { visitorId: order.visitorId },
    create: {
      visitorId: order.visitorId,
      reason: "chargeback",
      originalOrderId: orderId,
      chargebackAmount: amount,
    },
    update: {
      chargebackCount: { increment: 1 },
      totalChargebackAmount: { increment: amount },
      lastChargebackAt: new Date(),
    },
  });

  // Alert fraud team for pattern analysis
  await alertFraudTeam("chargeback_received", {
    visitorId: order.visitorId,
    orderId,
    amount,
    reason,
    customerEmail: order.customerEmail,
    ipAddress: order.ipAddress,
  });

  return res.json({ received: true });
});
```

***

### Database Schema Example

```sql
-- Orders with fraud prevention data
CREATE TABLE orders (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  customer_id UUID REFERENCES customers(id),
  amount DECIMAL(10, 2) NOT NULL,
  status VARCHAR(50) DEFAULT 'pending',
  
  -- Guardian fraud prevention
  visitor_id VARCHAR(255),
  ip_address INET,
  device_country VARCHAR(2),
  risk_score INTEGER,
  risk_reasons TEXT[],
  
  -- Card info (tokenized)
  card_last_four VARCHAR(4),
  card_brand VARCHAR(20),
  billing_country VARCHAR(2),
  
  created_at TIMESTAMP DEFAULT NOW()
);

-- Index for fraud lookups
CREATE INDEX idx_orders_visitor_id ON orders(visitor_id);
CREATE INDEX idx_orders_status ON orders(status);

-- Fraud blacklist
CREATE TABLE fraud_blacklist (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  visitor_id VARCHAR(255) UNIQUE NOT NULL,
  reason VARCHAR(100) NOT NULL,
  chargeback_count INTEGER DEFAULT 1,
  total_chargeback_amount DECIMAL(10, 2) DEFAULT 0,
  original_order_id UUID,
  last_chargeback_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Payment attempts (for velocity tracking)
CREATE TABLE payment_attempts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  visitor_id VARCHAR(255),
  status VARCHAR(50),  -- 'approved', 'declined', 'error'
  decline_reason VARCHAR(100),
  amount DECIMAL(10, 2),
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_payment_attempts_visitor_time 
  ON payment_attempts(visitor_id, created_at);
```

***

### Best Practices

#### Do

* **Store `visitorId`** with every order for chargeback correlation
* **Link chargebacks** back to device IDs to catch returning fraudsters
* **Use risk scoring** for graduated responses (approve/review/decline)
* **Monitor velocity** to catch card testing attacks
* **Flag for review** rather than auto-decline borderline cases
* **Track decline rates** per device to identify card testers

#### Don't

* **Don't reveal fraud detection logic** in error messages
* **Don't block VPNs outright** — many legitimate customers use them
* **Don't rely solely on AVS/CVV** — fraudsters often have full card data
* **Don't ignore small transactions** — they may be card testing probes
* **Don't delete fraud data** — historical patterns are valuable

***

### Key Metrics to Track

| Metric                          | Description                           | Target         |
| ------------------------------- | ------------------------------------- | -------------- |
| **Chargeback Rate**             | Chargebacks / Total Transactions      | < 0.5%         |
| **Fraud Detection Rate**        | Blocked fraudulent / Total fraudulent | > 90%          |
| **False Positive Rate**         | Legitimate blocked / Total blocked    | < 5%           |
| **Card Testing Blocks**         | Velocity-based blocks per day         | Monitor trends |
| **Returning Fraudster Catches** | Blacklisted devices blocked           | Track monthly  |

***

### Conclusion

Payment fraud is a continuous battle, but you don't have to fight blind. By linking every transaction to a persistent device identity, Guardian Stack lets you:

* **Catch returning fraudsters** even with new cards and identities
* **Stop card testing bots** before they find valid cards
* **Reduce chargebacks** by declining high-risk transactions
* **Identify fraud rings** by connecting related devices
* **Build institutional memory** that improves over time

The key insight: fraudsters can steal unlimited cards, but they have limited devices. Make the device your anchor point for fraud prevention.

***

{% hint style="success" %}
**Get Started:** [Sign up for Guardian Stack](https://dashboard.guardianstack.ai/) and protect your checkout today.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.guardianstack.ai/documentation/protect-your-implementation/payment-fraud-prevention.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
