> For the complete documentation index, see [llms.txt](https://docs.violet.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.violet.io/prism/payments/payouts/net-receipts.md).

# Net Receipts

How Net Receipts works: the per-retailer fee settings you control through the API, submitting per-bag transaction fees, how they change the commission split, the wait for a fee, refunds, and hold case

**Net Receipts** is a distribution model in which a Channel supplies the payment transaction fee it is charged for an order, and Violet uses that fee to adjust the split between the Merchant and the Channel. It is used by Channels whose contracts with their retailers are struck on net receipts terms: commission is taken on goods value after the transaction fee, and the retailer, not the Channel, bears that fee.

Under the standard model, commission is applied to the goods value and the transaction fee plays no part (see [Commission Rates](/prism/payments/payouts/commission-rates.md) and [Fee Structure](/prism/payments/payments-during-checkout/fee-structure.md)). Net Receipts changes that along two independent axes:

* **Commission basis**: commission can be computed on the goods value after the fee, instead of before it.
* **Fee remitter**: the fee can be recovered from the Merchant's payout and moved to the Channel, rather than absorbed by the Channel.

{% hint style="info" %}
These two settings are independent. A retailer can be configured for either, both, or neither. The combination is resolved **per bag**, so a Channel can run Net Receipts retailers and standard retailers side by side, including within the same multi-merchant order.
{% endhint %}

The fee you supply is separate from the payment provider's processing fee (for example Stripe's). That fee is still charged on every order and recorded on the distribution as `payment_provider_total_fee`, whatever fee you submit.

## How the Calculation Changes

For a bag whose retailer uses the fee, with an accepted transaction fee present:

```
commission_base = subtotal − discount − tax_inside_goods − (deduct_before_commission ? fee : 0)
commission      = commission_base × commission_rate
fee_withholding = (fee_remitter == MERCHANT) ? fee : 0

merchant_share  = bag_total − commission − app_remitted_tax − fee_withholding
channel_share   = commission − payment_provider_fee + app_remitted_tax + fee_withholding
```

`tax_inside_goods` is zero unless the retailer prices goods tax-inclusive and has a tax profile in force; tax comes off before the fee. See [Commission Base: Tax, Duty and Discounts](/prism/payments/payouts/commission-base.md). Tax follows your [tax remitter settings](/prism/payments/payment-settings/tax-remitter-settings.md) exactly as in the standard model, and duty reported on the order is paid to the retailer. The only Net Receipts additions are the `fee` subtraction in the commission base and the `fee_withholding` movement from the merchant leg to the channel leg.

### Example

<details>

<summary>Net Receipts order: commission on net basis, fee recovered from merchant</summary>

**Settings:** deduct before commission = true, fee remitter = MERCHANT

**Order Summary:**

* Goods subtotal: $100.00
* Discount: $0.00
* Transaction fee (supplied by Channel): $3.90
* Commission Rate: 15%

**Step-by-Step:**

1. Commission base = $100.00 − $3.90 = $96.10
2. Commission = $96.10 × 15% = $14.42
3. Merchant payout = $100.00 − $14.42 − $3.90 = $81.68
4. Channel receives = $14.42 + $3.90 = $18.32 *(before payment provider fees)*

The legs sum to the bag total exactly. Compared with the standard model, the Channel recovers its $3.90 fee from the merchant payout, and commission is taken on the reduced $96.10 base rather than the full $100.00.

</details>

## Before You Start

Two things are set by Violet for your app when it enables Net Receipts:

* **Your app may supply transaction fees.** Until this is on, fee submissions are recorded as rejected and a retailer cannot be set to use your fee.
* **Your app is on the channel-pays processing fee model,** the only model that applies a supplied fee to the split.

{% hint style="danger" %}
Do not set `deduct_transaction_fees_before_commission` to `true` or `transaction_fee_remitter` to `MERCHANT` on any retailer until Violet confirms both are in place for your app. On an app using a different processing fee model, every bag for that retailer is held (`CONFIG_CONFLICT`) and no money moves for it.
{% endhint %}

To enable Net Receipts for your app, [contact Violet support](/help/support-request.md).

## Setting a Retailer's Fee Settings

Once your app is enabled, you set each retailer's fee settings yourself, to match its contract. Each setting follows your app-wide value unless the retailer has its own.

| Setting                                     | Values                                    | What it decides                                                                                                           | Platform default          |
| ------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| `fee_source_mode`                           | `APP_PROVIDED`, `PAYMENT_PROVIDER_ACTUAL` | Where the fee used for Net Receipts comes from. Net Receipts needs `APP_PROVIDED`: the fee your app submits for each bag. | `PAYMENT_PROVIDER_ACTUAL` |
| `deduct_transaction_fees_before_commission` | `true`, `false`                           | Whether commission is calculated on the goods value after the fee.                                                        | `false`                   |
| `transaction_fee_remitter`                  | `APP`, `MERCHANT`                         | Who bears the fee. `MERCHANT` moves it from the retailer's share to yours.                                                | `APP`                     |

On an app enabled for Net Receipts, the two settings change the split only when `fee_source_mode` resolves to `APP_PROVIDED`. Your app-wide values are set by Violet; ask Violet if you want a different default for every retailer.

### Read the settings

```
GET /v1/merchants/{merchant_id}/app_relationships/{app_id}/fee_settings
```

```json
{
  "merchant_id": 10000,
  "app_id": 10001,
  "overrides": {
    "fee_source_mode": null,
    "deduct_transaction_fees_before_commission": true,
    "transaction_fee_remitter": "MERCHANT"
  },
  "effective": {
    "fee_source_mode": { "value": "APP_PROVIDED", "source": "APP" },
    "deduct_transaction_fees_before_commission": { "value": true, "source": "MERCHANT" },
    "transaction_fee_remitter": { "value": "MERCHANT", "source": "MERCHANT" }
  }
}
```

`overrides` holds the retailer's own values (`null` means it follows your app). `effective` gives the value applied to each setting and where it came from: `MERCHANT` (the retailer's own value), `APP` (your app-wide value) or `DEFAULT` (the platform default).

### Change the settings

```
PATCH /v1/merchants/{merchant_id}/app_relationships/{app_id}/fee_settings
Content-Type: application/json
```

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X PATCH "https://sandbox-api.violet.io/v1/merchants/10000/app_relationships/10001/fee_settings" \
  -H "X-Violet-Token: $VIOLET_TOKEN" \
  -H "X-Violet-App-Secret: $VIOLET_APP_SECRET" \
  -H "X-Violet-App-Id: 10001" \
  -H "Content-Type: application/json" \
  -d '{"deduct_transaction_fees_before_commission": true, "transaction_fee_remitter": "MERCHANT"}'
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
const res = await fetch(
  "https://sandbox-api.violet.io/v1/merchants/10000/app_relationships/10001/fee_settings",
  {
    method: "PATCH",
    headers: {
      "X-Violet-Token": process.env.VIOLET_TOKEN,
      "X-Violet-App-Secret": process.env.VIOLET_APP_SECRET,
      "X-Violet-App-Id": "10001",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      deduct_transaction_fees_before_commission: true,
      transaction_fee_remitter: "MERCHANT",
    }),
  }
);
console.log((await res.json()).effective);
```

{% endtab %}

{% tab title="Python" %}

```python
import os
import requests

res = requests.patch(
    "https://sandbox-api.violet.io/v1/merchants/10000/app_relationships/10001/fee_settings",
    headers={
        "X-Violet-Token": os.environ["VIOLET_TOKEN"],
        "X-Violet-App-Secret": os.environ["VIOLET_APP_SECRET"],
        "X-Violet-App-Id": "10001",
    },
    json={
        "deduct_transaction_fees_before_commission": True,
        "transaction_fee_remitter": "MERCHANT",
    },
)
res.raise_for_status()
print(res.json()["effective"])
```

{% endtab %}
{% endtabs %}

* A field you leave out is unchanged. A field sent as `null` clears the retailer's own value, so it follows your app again.
* `deduct_transaction_fees_before_commission` must be a JSON boolean, not a string. Enum values are case-sensitive.
* `APP_PROVIDED` is refused with a `400` unless your app has been enabled to supply fees.
* The update is all or nothing: if any field is invalid, nothing changes.
* The same permissions and errors apply as for the [return commission policy](/prism/payments/payouts/return-commission-policy.md#change-the-policy): the token's developer must own the app, and the retailer must be enabled on it.

**When a change applies.** New settings apply to bags whose split is calculated after the change, which happens a few seconds after each order is accepted. Splits already calculated are not recalculated. Settings are cached, so allow up to an hour for a change to reach new orders.

See the API reference: [Get Transaction Fee Settings](/api-reference/merchants/app-relationships/get-fee-settings.md) and [Update Transaction Fee Settings](/api-reference/merchants/app-relationships/update-fee-settings.md).

### Matching settings to contracts

| Contract                    | `deduct_transaction_fees_before_commission` | `transaction_fee_remitter` | [Return policy](/prism/payments/payouts/return-commission-policy.md) |
| --------------------------- | ------------------------------------------- | -------------------------- | -------------------------------------------------------------------- |
| Sale-and-return (SAR)       | `false`                                     | `APP`                      | `REVERSE`                                                            |
| SAR, retailer bears the fee | `false`                                     | `MERCHANT`                 | `REVERSE`                                                            |
| SAR Net Receipts            | `true`                                      | `MERCHANT`                 | `REVERSE`                                                            |
| Sale-based retention (SBR)  | `false`                                     | `APP`                      | `RETAIN`                                                             |
| SBR, retailer bears the fee | `false`                                     | `MERCHANT`                 | `RETAIN`                                                             |
| SBR Net Receipts            | `true`                                      | `MERCHANT`                 | `RETAIN`                                                             |

Contract names vary between channels. What matters is the three answers: is commission taken after the fee, who bears the fee, and is commission kept on a refund.

## Sending Transaction Fees

Channels submit the fee for a single bag. The value you send is **authoritative**: Violet stores it as-is and does not recompute it.

```
PUT /v1/bags/{bag_id}/transaction_fee
```

**Authentication** (request headers): `X-Violet-Token`, `X-Violet-App-Secret`, `X-Violet-App-Id`. The submitting app must own the bag, or the request is rejected with `insufficient_permissions` (403). The fee's source app is taken from the authenticated `X-Violet-App-Id`; you do not supply it in the body.

**Request body:**

| Field                               | Type    | Required    | Description                                                                                               |
| ----------------------------------- | ------- | ----------- | --------------------------------------------------------------------------------------------------------- |
| `transaction_fee_total_amount`      | integer | Yes         | Total fee in minor units (e.g. cents). Authoritative, and the only amount used in the split. Must be ≥ 0. |
| `transaction_fee_percentage_amount` | integer | Yes         | Percentage component, minor units, stored for reporting. Send `0` if not applicable.                      |
| `transaction_fee_fixed_amount`      | integer | Yes         | Fixed component, minor units, stored for reporting. Send `0` if not applicable.                           |
| `currency`                          | string  | Yes         | ISO 4217. Must match the bag's **base currency** (compared case-insensitively).                           |
| `source_type`                       | string  | Yes         | Must be `APP_PROVIDED`.                                                                                   |
| `source_provider`                   | string  | No          | Free-form source identifier.                                                                              |
| `source_reference_id`               | string  | No          | Your reference ID, for reconciliation.                                                                    |
| `idempotency_key`                   | string  | Recommended | Safe-retry key.                                                                                           |

Violet does not check that the percentage and fixed components add up to the total. Each must be present and not negative.

**Outcomes** (all HTTP 200):

| `outcome`                  | Meaning                                                                                                                                                                                      |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `accepted`                 | The fee is recorded and will be used if the retailer's settings use it.                                                                                                                      |
| `duplicate_accepted`       | The same submission was sent again under the same `idempotency_key`. The existing record is returned.                                                                                        |
| `config_conflict_rejected` | Your app is not enabled to supply fees, or the retailer's `fee_source_mode` is not `APP_PROVIDED`. Stored with `error_code` `CONFIG_DISALLOWS_APP_PROVIDED_FEE`; it has no effect.           |
| `late_rejected`            | The bag's split was already calculated and is not waiting for a fee. `error_code` is `FEE_ALREADY_APPLIED` if the split already carries a fee, otherwise `SUBMISSION_TOO_LATE`. Not applied. |

Other responses: the same `idempotency_key` sent with different values returns `409` `duplicate_conflict`. Validation failures (bag not found, wrong owner, missing or negative amounts, currency mismatch, wrong source type) return the standard 4xx error envelope.

{% hint style="success" %}
**Safe to send for every retailer.** You can submit a fee for every bag. It changes the split only for retailers whose settings use it: deducted before commission, or borne by the retailer. For every other retailer the fee is recorded, or rejected with `config_conflict_rejected`, and the standard calculation applies unchanged.
{% endhint %}

* **One fee per bag.** Send one fee for each bag. If two different fees are accepted for the same bag before its split is calculated, the bag is held for review (`DUPLICATE_ACCEPTED_FEE`). A fee cannot be corrected once the split has used it.
* **Reading fees back.** There is no endpoint to list submitted fees. Keep the response to each `PUT`, which is the stored record.

## The Wait for a Fee

Your app usually sends the fee a few seconds after the order is accepted, and Violet normally calculates a bag's split a few seconds after acceptance too. So for any retailer whose settings use the fee, Violet **waits for the fee before splitting the bag**.

A bag waits when your app is enabled to supply fees, the retailer's `fee_source_mode` is `APP_PROVIDED`, and the retailer either deducts the fee before commission or bears it. While it waits:

* nothing moves for the bag;
* its distributions show `status` `FAILED` with `idempotency_key` `NET_RECEIPTS_HOLD:AWAITING_FEE`.

When the fee arrives, Violet calculates the split with it and releases the bag. The retailer's share then follows its usual transfer timing: the next daily run for automatic transfers, its schedule for [scheduled transfers](/prism/payments/payment-settings/scheduled-transfers.md), or pending for custom and external transfers.

**After 72 hours** with no fee, the marker changes to `NET_RECEIPTS_HOLD:MISSING_FEE` and Violet is alerted to follow up with you. The bag keeps waiting: a fee that arrives later is still accepted and releases it.

Violet never applies a default or estimated fee in place of yours.

For a retailer whose settings do not use the fee, nothing waits, and a fee is accepted whenever it arrives.

## Refunds

* **The transaction fee is never returned on a refund,** whatever the retailer's return policy. It stays where the sale put it: with you, or recovered from the retailer if it bears the fee.
* **Commission follows the** [**return policy**](/prism/payments/payouts/return-commission-policy.md)**.** Under `REVERSE`, the commission reversed is the commission recorded on the sale, prorated on the share of goods refunded. Because the sale's commission was already taken on the net-of-fee base, there is no separate fee adjustment. Under `RETAIN`, you keep the commission and Violet [recovers it](/prism/payments/payouts/commission-recovery.md) from the retailer.
* **A full refund does not take the withheld fee back.** When the retailer bears the fee, a full refund reverses what the retailer was paid, not the fee you withheld.
* Fee provenance is preserved on the refund records for audit.

## Hold Conditions

To protect against bad splits, a bag is **held** (no funds move) rather than processed in these cases. Each shows on the bag's distributions as `status` `FAILED` with `idempotency_key` `NET_RECEIPTS_HOLD:<reason>`.

| Reason                   | When                                                                                                        | What to do                                                                            |
| ------------------------ | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `AWAITING_FEE`           | The bag is waiting for your fee.                                                                            | Send the fee.                                                                         |
| `MISSING_FEE`            | No fee after 72 hours.                                                                                      | Send the fee; Violet will also contact you.                                           |
| `DUPLICATE_ACCEPTED_FEE` | More than one fee was accepted for the bag.                                                                 | Contact Violet support with the correct fee.                                          |
| `FEE_RESULT_INVALID`     | The fee would make the commission base or a payout negative, for example a fee larger than the goods value. | Contact Violet support.                                                               |
| `CONFIG_CONFLICT`        | The retailer's settings use the fee, but your app is not on the channel-pays processing fee model.          | Clear the retailer's fee settings, or ask Violet to enable Net Receipts for your app. |

A zero fee is valid and changes nothing. Distributions can carry the same `NET_RECEIPTS_HOLD:` marker for reasons unrelated to fees, such as a bag cancelled after payment was captured without a matching refund.

## Common Questions

**Do I have to send a fee for retailers that are not on Net Receipts?** No, but it is safe to. It is recorded and changes nothing for them.

**Which fee is the Payment Provider Fee on a distribution?** That is the processing fee charged by the payment provider, paid by the channel. It is a different fee from the one you submit, and submitting a fee does not replace it.

**What happens if our fee service is down for a day?** Bags for retailers that use the fee wait. Send the fees when you can and the bags are released. Nothing is lost, and no estimate is applied.

## Related

* [Commission Base: Tax, Duty and Discounts](/prism/payments/payouts/commission-base.md)
* [Return Commission Policy](/prism/payments/payouts/return-commission-policy.md)
* [Commission Rates](/prism/payments/payouts/commission-rates.md)
* [Fee Structure](/prism/payments/payments-during-checkout/fee-structure.md)
* [Distributions](/prism/payments/payouts/distributions.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

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

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.violet.io/prism/payments/payouts/net-receipts.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
