A user orders food delivery. On their bank statement, this single act of ordering a burrito generates four separate line items: the base order amount, a delivery fee, a service fee, and a tip. Each appears as a distinct transaction, often on different dates (the authorization holds and final settles do not always align), sometimes under slightly different merchant description strings.
From a cashflow perspective, this is one purchase. But in the transaction feed, it looks like four separate spending events. If your spending breakdown screen sums those four charges without collapsing them, the user sees inflated spending in the food category and a confusing transaction list. This is not a niche edge case: split charging is standard practice for rideshare platforms, food delivery apps, marketplace purchases with per-item shipping, and some subscription models with add-on charges.
Detecting and collapsing these splits is a non-trivial problem. This post explains how we approach it in the enrichment pipeline.
Why Splits Are Hard to Detect
The naive approach, match transactions that share a merchant name and occur within a short time window, fails quickly in practice.
First, the merchant description strings for split charges are often different from each other. A rideshare trip might generate: LYFT *RIDE WED 9PM for the base fare, and LYFT *CANCEL WAIT FEE for an additional charge, and LYFT *SERVICE FEE for the platform fee. These are the same underlying transaction from a user's perspective, but the strings are not obviously related unless you know that LYFT * is a prefix pattern for a platform that systematically splits its charges.
Second, the timing window is unreliable. Authorization holds can appear days before settlement. Add-on charges sometimes settle after the original authorization clears. A strict time-window approach will miss splits that arrive asynchronously.
Third, not every pair of nearby same-merchant transactions is a split. A user who makes two separate Uber Eats orders on the same evening has two distinct purchases that should not be collapsed. The split detection logic needs to distinguish "four charges that belong to one order" from "two separate orders from the same platform."
Platform-Specific Charge Patterns
The most reliable approach for high-volume platforms is to build explicit knowledge of their charge structure. Platforms that systematically split charges follow predictable patterns that can be encoded as detection rules.
For food delivery platforms, the pattern is typically: a primary charge (the order subtotal) followed by ancillary charges (delivery fee, service fee, tip, sometimes a small-order fee) within a 24-hour window. The ancillary charges are usually smaller than the primary charge and often have characteristic string patterns: "FEE," "TIP," "DELIVERY" as suffixes or prefixes to the platform name.
For rideshare platforms, the pattern is similar but with different components: base fare, tolls (if applicable), wait time charges, and tip. Platform-specific string formats help identify these: UBER* TRIP versus UBER* TIP is a clear signal. LYFT * prefix patterns are consistent within the platform.
For online marketplaces, splits occur when multi-item orders ship in separate packages. Each shipment becomes a separate charge. The connection signal is weaker here: the charges appear under the same merchant but with no structural relationship in the description strings. Time-window proximity is the primary detection signal, combined with the absence of a rounding pattern (split charges from a single order typically sum to a recognizable total).
The Detection Algorithm
Our split detection runs as a post-enrichment step after merchant resolution. The algorithm operates in three phases.
Phase one: merchant-level split candidacy. For each transaction, we check whether the resolved merchant is in a set of known split-capable platforms. If yes, the transaction is flagged as a potential split participant. This set is maintained manually and currently includes roughly 35 platforms that we have confirmed generate systematic split charges.
Phase two: candidate grouping. For each split-capable transaction, we look at a time window (48 hours forward and backward by default, configurable per merchant based on their settlement patterns) for other transactions from the same platform on the same account. We group transactions that fall within the window and share the platform merchant.
Phase three: group validation. A group of transactions is confirmed as a split if it passes at least two of three validation conditions: the charges sum to a round-ish amount (within a few cents of a whole dollar or half-dollar, which is characteristic of order totals), at least one charge matches a known ancillary pattern (tip, fee, delivery), and the group size is within the expected range for that platform (food delivery splits usually have two to five charges; more than six is suspicious and likely separate orders).
Groups that pass validation are returned in the enrichment response with a split_group_id field, allowing the calling application to link the charges and display them as a single logical transaction.
Handling False Positives and False Negatives
The hardest category of errors is false positives: collapsing transactions that should not be collapsed. A user who orders from the same food delivery platform twice in one evening, each a small order, can look to the algorithm like a single split order. The amount sum might happen to be round. The charge count might be within the expected range.
We handle this primarily through the group size constraint and an amount plausibility check. A food delivery order that totals $89 is plausible but suspicious for a single order. We return a lower confidence score on the split detection for amounts outside the typical per-platform range, which the calling application can use to decide whether to collapse or display separately.
False negatives (failing to detect a split that exists) are less harmful from a UX perspective: the user sees multiple charges where they expect one, which is annoying but not incorrect. False positives (collapsing separate transactions into one) are more harmful: the user sees spending disappear from their transaction list. We bias toward lower false-positive rates even at the cost of more false negatives.
We are not saying this system is perfect. The 35-platform known-split set covers the vast majority of split transactions by volume, but it misses the long tail of smaller platforms that also split charges. If your user base has heavy spending with a platform not in our detection set, the splits will not be detected. The confidence score reflects this: platforms outside the known set return a split confidence of 0, meaning "no split grouping attempted."
What the Response Looks Like
When split detection fires, the enrichment response includes group metadata on each participating transaction:
{
"transaction_id": "txn_a1b2c3",
"amount": -23.45,
"merchant_name": "DoorDash",
"category_code": "food_drink.delivery",
"split_detection": {
"is_split": true,
"group_id": "split_grp_9f8e7d",
"role": "primary",
"group_total": -28.70,
"group_member_count": 3,
"confidence": 0.88
}
}
{
"transaction_id": "txn_d4e5f6",
"amount": -3.25,
"merchant_name": "DoorDash",
"category_code": "food_drink.delivery",
"split_detection": {
"is_split": true,
"group_id": "split_grp_9f8e7d",
"role": "fee",
"group_total": -28.70,
"group_member_count": 3,
"confidence": 0.88
}
}
The role field (primary, fee, tip, tax) tells the application which charge is the main order and which are ancillary. The group_total is the sum of all group members. The application can decide how to display this: some PFM apps show only the primary charge in the main list view and expand to show components on tap; others show the group total as a single line item.
What This Does for Cashflow Accuracy
The practical impact of split detection on cashflow views is larger than it might seem. In user testing we have done with prototype apps, users consistently over-estimate their food delivery spending when splits are not collapsed. Seeing four charges for one order pattern-matches to "I spent a lot on food delivery," even when the total is comparable to a single restaurant visit.
Collapsing splits correctly makes cashflow views more trustworthy. Users trust data they can verify against their own experience. If their transaction list matches what they remember ordering, they engage with the spending insight layer. If the list looks wrong, they disengage.
Split detection is one of those features that users never explicitly ask for but notice immediately when it is absent. Getting it right is worth the implementation complexity.