Skip to content

Banking, insurance and financeBI, AI and ML for the business

Card payment fraud

Every card payment leaves a row: amount, merchant, country, time. In Muvia a Python function written by your team compares each payment with the customer's habits and gives it a score that says why, and an alarm tells the fraud team when suspicious payments start to cluster.

Illustrative scenario: it describes a typical case, not a customer project.

One fraud on its own gets noticed. A wave at night, often only in the morning.

Authorisation checks stop the obvious cases. What is left are payments that look plausible one by one but odd for that customer: an hour never seen before, a new country, a category they never buy in. When dozens arrive in the same night, it is an attack.

To see it you need each customer's habits next to each payment, a score that says why a payment is suspicious and an alarm that looks at the whole picture, not the single case.

Who it's for
Fraud and transaction monitoring teams, the bank's data scientists, risk management and card operations.
Card payments in hourly files over SFTP, customers and cards from the core banking system on Oracle, disputes from a REST API and merchant categories in Excel flow into Muvia, where the team's function scores every payment; out come a table of scores, an anomaly dashboard, an alarm on the wave and the weekly summary.

In Muvia, step by step

Real product screens, recorded on a project with sample data.

1 of 5

The video · Every card payment leaves a row: amount, merchant, country, time. In Muvia a Python function written by your team compares each payment with the customer's habits and gives it a score that says why, and an alarm tells the fraud team when suspicious payments start to cluster.

What you get

A score that explains itself
Every suspicious payment carries its reasons: whoever looks at it knows why it was flagged, without opening the code.
The wave, not just the case
The alarm looks at how many suspicious payments arrive together, so a night-time attack shows up as one.
The model stays the team's
Signals, weights and thresholds are the bank's call; a new model is a new version of the function, tried before it goes live.
Every decision stays on record
Who took the episode, what they blocked and why stays in the alarm register.

For the technical team

How it is built in Muvia

  1. 1

    Bring the payments into Muvia

    A file every hour over SFTP appends the new transactions to the payments table; core banking and disputes are copied on a schedule. Every source becomes a table you can query.

  2. 2

    Work out each customer's habits

    A query works out each customer's usual hours, countries, categories and typical amount over recent months, and saves them as a dataset with stable fields.

  3. 3

    Write the score in Python

    A function compares each payment with those habits and adds five signals into a score, with the reasons written alongside. The team can swap it for a scikit-learn or ONNX model trained elsewhere; you try it first in a trial run, which writes nothing.

  4. 4

    See the wave

    A dashboard shows suspicious payments by hour and category in a heatmap, and score against amount: a night-time attack stands out at a glance.

  5. 5

    Set an alarm on the whole

    A flow opens an episode when payments above the threshold exceed a count within an hour. In the register the fraud team takes it on and notes what they blocked; the weekly summary is a notebook.

Python function

import numpy as np

def transform(inputs, params, ctx):
    df = inputs["rows"]
    signals = {
        "unusual hour": df["customer_hour_share"] < 0.02,
        "new country": df["country"] != df["usual_country"],
        "new category": df["customer_category_share"] == 0,
        "high amount": df["amount"] > 3 * df["customer_median_amount"],
        "burst": df["payments_last_hour"] >= params["burst"],
    }
    weights = params["weights"]
    out = df[["payment_id", "card_id", "timestamp", "amount"]].copy()
    out["score"] = sum(weights[k] * s.astype(int) for k, s in signals.items())
    names = np.array(list(signals))
    fired = np.column_stack([s.to_numpy(dtype=bool) for s in signals.values()])
    out["reasons"] = [", ".join(names[row]) for row in fired]
    out["suspicious"] = out["score"] >= params["threshold"]
    ctx.logger.info("payments scored: %d", len(out))
    return {"rows": out}
Five signals against the customer's habits, weighted by the team: every payment comes out with a score and the reasons behind it.
The data it needs
  • Card payments with amount, merchant, category, country and channel, in hourly files received over SFTP
  • Customers and cards from the core banking system on Oracle
  • Customer disputes and reports, read from a REST API
  • Merchant categories and risk levels, from an Excel file
Parts of Muvia used

Bring us a question you can't answer today.

We start from a real question your business has and walk the path from source to dashboard with your systems, not a demo dataset.