> ## Documentation Index
> Fetch the complete documentation index at: https://developer.tazapay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Payin Reversal: Risk-Driven Reversed State and Webhook Events

**Release type:** Enhancement (Backward-compatible)
**Applies to:** Payin, Payment Attempt

## What's new

A new terminal state **`reversed`** has been introduced for Payin and Payment Attempt objects. This state is triggered by an **automated payer name verification check** run after PSP payment approval on eligible push-based Alternative Payment Methods (APMs).

When the name provided by the buyer at checkout does not match the name returned by the PSP after payment (a `NO_MATCH` result), the transaction is moved to `reversed` and a full refund is automatically initiated back to the buyer. The net impact on the merchant balance is zero.

***

## Why this was introduced

Push-based APMs (PayNow, PIX, UPI, Promptpay, etc.) debit funds immediately, and the payer's identity is only confirmed after PSP approval. This creates exposure to social-engineering fraud where a payment is made by a third party on behalf of the buyer. The `reversed` state is Tazapay's automated response to this pattern — transactions where the payer identity cannot be verified are reversed before they reach `succeeded`.

***

## Eligibility

The name match check — and therefore the `reversed` state — only applies when **all** of the following conditions are true:

* The payment method is a push-based APM in Tazapay's risk-check allowlist
* The PSP returns a `payer_name` in its success response
* The merchant has **Name Match Check enabled** (configured via the Tazapay Ops dashboard)
* The customer's total payin volume over the trailing 30 days exceeds the configured threshold (default: **0 USD**, meaning the check applies to every eligible transaction)

Transactions that do not meet all conditions proceed directly to `succeeded` with no change to the existing flow.

***

## Updated Payin State Flow

After PSP approval, an internal risk check runs between `processing` and the terminal state. This check is not visible as a separate state — it completes within seconds.

```
processing → succeeded   Name match: FULL_MATCH, PARTIAL_MATCH
processing → reversed    Name match: NO_MATCH (refund auto-initiated)
```

`reversed` is a terminal state. A payin that reaches `reversed` will never emit `payin.succeeded`.

***

## Name Match Outcomes

| Match Result    | Terminal State | Outcome                                        |
| --------------- | -------------- | ---------------------------------------------- |
| `FULL_MATCH`    | `succeeded`    | Normal flow — funds credited to merchant       |
| `PARTIAL_MATCH` | `succeeded`    | Normal flow — funds credited to merchant       |
| `NO_MATCH`      | `reversed`     | Refund initiated — net merchant impact is zero |

***

## Balance & Fee Impact for `reversed` Transactions

Funds flow through the merchant balance even for `reversed` transactions to maintain ledger consistency:

1. **Payin credit** — merchant balance is credited (same as `succeeded`)
2. **Refund debit** — full amount is immediately debited back via a system-initiated refund

The net merchant balance impact is **zero**. Tazapay bears both the payin processing fee and the refund processing fee — no fees are charged to the merchant or the buyer.

***

## Automatic Refund

When a payment attempt is reversed, Tazapay automatically creates a refund and initiates it with the PSP. Merchants do not need to take any action.

The refund is created with:

| Field          | Value            |
| -------------- | ---------------- |
| `source`       | `payin_reversal` |
| `reason`       | `Payin Reversal` |
| `initiated_by` | `system`         |
| `fee_bearer`   | `tazapay`        |

Merchants will receive a **`refund.created`** webhook immediately after the refund is saved. The refund ID is only available in that webhook payload — it is not included in the `payment_attempt.reversed` payload or the Get Payin response. Merchants should capture the `refund.id` from `refund.created` to track the refund status via `GET /v3/refund/{refund_id}`.

***

## Notifications

| Recipient | Channel                              | Configurable                                              |
| --------- | ------------------------------------ | --------------------------------------------------------- |
| Buyer     | Email                                | No — always sent                                          |
| Merchant  | Email                                | Yes — requires email event to be enabled at account level |
| Merchant  | Webhook (`payment_attempt.reversed`) | Yes — enabled by default                                  |

The buyer receives an email notifying them that their payment was reversed and a full refund has been initiated. The merchant email (if enabled) includes the transaction amount, buyer details, and a link to view the transaction in the dashboard.

***

## Webhook Events

| Event                      | Trigger                                        | New?                | Default (on/off) |
| -------------------------- | ---------------------------------------------- | ------------------- | ---------------- |
| `payment_attempt.reversed` | Payment attempt `status` changes to `reversed` | Yes                 | On               |
| `refund.created`           | Refund created for the reversed payment        | No — existing event | Off              |

***

## API Changes

### Get Payin — `GET /v3/payin/{id}`

Two new fields have been added to the response:

| Field                   | Type                   | Description                                                                                          |
| ----------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------- |
| `reversed_at`           | string (ISO timestamp) | Timestamp when the payin was reversed. `null` unless `status` is `reversed`.                         |
| `risk_check`            | object / null          | Risk check result that determined the terminal state. `null` when no name match check was performed. |
| `risk_check.result`     | string                 | Match outcome: `FULL_MATCH`, `PARTIAL_MATCH`, `NO_MATCH`, or `EMAIL`.                                |
| `risk_check.buyer_name` | string                 | Name provided by the buyer at checkout.                                                              |
| `risk_check.payer_name` | string                 | Name returned by the PSP after payment.                                                              |
| `risk_check.checked_at` | string (ISO timestamp) | Timestamp when the risk check was performed.                                                         |

***

## Backward Compatibility

This enhancement is **non-breaking**:

* Transactions on payment methods not in the APM allowlist, or for merchants without Name Match Check enabled, continue to flow directly to `succeeded` — no change.
* `succeeded` and `cancelled` state semantics and flows are unchanged.
* `reversed_at` is `null` in the response for payins that are not in `reversed` status. `risk_check` is `null` for payins that were not subject to a name match check.
* `payment_attempt.reversed` is a new webhook event type, enabled by default. Merchants who have configured a webhook endpoint will begin receiving it automatically.
