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

# Taxes

> Automatically calculate and collect sales tax on payments and subscriptions.

PayNext integrates with tax providers to automatically calculate, collect, and report taxes on your transactions. Configure tax once, and PayNext handles the rest—from calculating the correct rate based on customer location to reporting transactions for filing. See [Integrations](/integrations/overview) for supported tax providers.

## How it works

<Steps>
  <Step title="Customer initiates payment">
    PayNext sends transaction details to your tax provider.
  </Step>

  <Step title="Tax calculated">
    Provider returns tax amount based on customer location and your tax behavior setting (`exclusive` or `inclusive`).
  </Step>

  <Step title="Payment authorized">
    Payment authorized for the total amount (subtotal + tax for exclusive, or full amount for inclusive).
  </Step>

  <Step title="Payment settles">
    Tax transaction submitted to provider for reporting.
  </Step>

  <Step title="Refund processed (if applicable)">
    Tax transaction reversed with provider.
  </Step>
</Steps>

## How tax location is determined

PayNext determines the customer's tax jurisdiction based on the payment method and available data:

| Payment method        | Tax location source                                                                      |
| :-------------------- | :--------------------------------------------------------------------------------------- |
| Apple Pay, Google Pay | Billing address from the wallet                                                          |
| All other methods     | `zip_code` and `state` passed in the customer object                                     |
| Fallback              | Cloudflare geolocation from [device fingerprint](/guides/advanced/device-fingerprinting) |

For US customers, pass `zip_code` and `state` in the customer object when creating the checkout session. These two fields are the minimum required for tax calculation—in most cases, the state can be detected automatically from the zip code. For the most accurate calculation, pass the full address object.

If address data is not provided, PayNext uses geolocation data from the customer's device. Learn more about [what data PayNext collects](/guides/advanced/device-fingerprinting) to determine tax location.

### Address data and audit risk

The accuracy of your tax location data affects audit risk. When a payment method provides a full billing address (like Apple Pay or Google Pay), PayNext automatically uses it for tax calculation.

| Scenario                      | Approach                                               | Audit risk |
| :---------------------------- | :----------------------------------------------------- | :--------- |
| Full billing address          | Street-level geocoding                                 | Low        |
| Partial address (zip + state) | State-level tax calculation                            | Low        |
| No address provided           | Cloudflare geolocation (zip, state, city, coordinates) | Medium     |

When no customer address is provided, PayNext collects [fingerprint data](/guides/advanced/device-fingerprinting) that includes zip code, state, city, coordinates, and IP address from the customer's device. This approach carries medium audit risk because:

* **Documented methodology** — Consistent, defensible process with detailed location data
* **Regulatory precedent** — Texas PLR guidance confirms IP-based geolocation is acceptable when addresses aren't collected
* **Audit trail** — All geolocation data is logged and available for compliance review

### Location mismatch handling

When creating a client session with customer country set to US but no address data available (no `zip_code`/`state` provided and payment method is not Apple Pay or Google Pay), PayNext falls back to device fingerprint for tax location.

If the fingerprint indicates the customer is outside the US (e.g., Canada), PayNext does not calculate US sales tax for that transaction. The payment processes without tax, and the API returns the ["tax doesn't apply"](#when-tax-doesnt-apply) response structure. This occurs because the customer's actual location—based on fingerprint data—is outside the tax provider's supported jurisdiction.

<Warning>
  **Disclaimer**: PayNext is not a tax provider. The information on this page is for informational purposes only and should not be considered tax advice. Audit risk assessments reflect our opinion, not guidance from a tax professional. Consult a qualified tax advisor for advice specific to your situation.
</Warning>

## Configuration

Tax configuration follows a hierarchy—set defaults globally and override tax collection at the plan level when needed.

### Global settings

Configure default tax behavior in **Dashboard → Integrations → Tax**:

| Setting                   | Description                                                                                                                                                                                                               |
| :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Tax provider              | Your connected tax provider (e.g., Numeral)                                                                                                                                                                               |
| Tax behavior              | `exclusive` (tax added on top) or `inclusive` (tax included in price)                                                                                                                                                     |
| Product category          | Product category for tax rate determination—select from the drop-down menu. Each tax provider has its own product categories (e.g., `GENERAL_MERCHANDISE`, `SAAS_GENERAL`). Defaults to `GENERAL_MERCHANDISE` if not set. |
| Tax registrations         | Jurisdictions where you'd like to collect tax                                                                                                                                                                             |
| Collect tax automatically | When enabled, tax is calculated for all payments by default. When disabled, tax is only collected for plans explicitly configured to collect.                                                                             |

### Plan-level settings

Override global tax collection for specific plans in **Dashboard → Plans → \[Plan Name]**.

Each plan has a `collect_tax` setting that controls whether tax is collected for payments under that plan:

| `collect_tax`  | Description                                                                              |
| :------------- | :--------------------------------------------------------------------------------------- |
| `DEFAULT`      | Uses the global "Collect tax automatically" setting from your tax provider configuration |
| `COLLECT`      | Always collect tax for this plan, regardless of the global setting                       |
| `DONT_COLLECT` | Never collect tax for this plan, regardless of the global setting                        |

When you retrieve plan details via API, the `tax` object reflects this configuration:

<Accordion title="Example plan response">
  ```json theme={"system"}
  {
    "id": "plan_1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "name": "Pro Plan",
    "tax": {
      "collect_tax": "COLLECT"
    }
  }
  ```
</Accordion>

<Tip>
  Use `DONT_COLLECT` for plans where tax should never apply (e.g., internal test plans or donation-based plans). Leave most plans on `DEFAULT` so they follow your global tax configuration.
</Tip>

<Note>
  Tax behavior (`exclusive` or `inclusive`) and product category are configured globally on the tax provider, not per plan. All plans that collect tax use the same behavior and product category.
</Note>

## Tax in API responses

All payments include a `tax` object. The structure varies based on whether tax was calculated for the transaction.

### When tax applies

When tax is enabled for the customer's location, the `tax` object includes provider details and calculation status:

<Accordion title="Example: Tax calculated ($100 + 7% tax)">
  ```json theme={"system"}
  {
    "id": "pay_e8a1b2c3-d4f5-6789-abcd-ef0123456789",
    "amount": 10700,
    "amount_usd": 10700,
    "currency_code": "USD",
    "payment_status": "SETTLED",
    
    "tax": {
      "provider": {
        "id": "b2c3d4e5-6789-01ab-cdef-2345678901bc",
        "type": "NUMERAL"
      },
      "calculation_id": "calc_xxx",
      "transaction_id": "txn_xxx",
      "amount_subtotal": 10000,
      "amount_subtotal_usd": 10000,
      "amount_tax": 700,
      "amount_tax_usd": 700,
      "behavior": "exclusive",
      "status": "submitted"
    }
  }
  ```
</Accordion>

<Note>
  When customers pay in other currencies, use `amount_subtotal_usd` and `amount_tax_usd` to see the USD equivalent for reporting.
</Note>

### When tax doesn't apply

When tax is not enabled for the customer's location (no tax provider configured, location not in your tax registrations, or plan set to `DONT_COLLECT`), the `provider` field is `null` and `amount_tax` is `0`:

<Accordion title="Example: Tax not applicable ($100, no tax)">
  ```json theme={"system"}
  {
    "id": "pay_e8a1b2c3-d4f5-6789-abcd-ef0123456789",
    "amount": 10000,
    "amount_usd": 10000,
    "currency_code": "USD",
    "payment_status": "SETTLED",
    
    "tax": {
      "provider": null,
      "amount_subtotal": 10000,
      "amount_subtotal_usd": 10000,
      "amount_tax": 0,
      "amount_tax_usd": 0
    }
  }
  ```
</Accordion>

Use the `provider` field to determine if tax was calculated:

* `provider` is present → tax was calculated, check `status` for the outcome
* `provider` is `null` → tax not applicable for this transaction

### Tax fields

| Field                 | Description                                                                                     |
| :-------------------- | :---------------------------------------------------------------------------------------------- |
| `provider.id`         | PayNext internal ID for your tax provider integration                                           |
| `provider.type`       | Tax provider type (e.g., `NUMERAL`)                                                             |
| `calculation_id`      | Tax provider's calculation reference                                                            |
| `transaction_id`      | Tax provider's transaction reference (populated after settlement)                               |
| `amount_subtotal`     | Revenue amount pre-tax                                                                          |
| `amount_subtotal_usd` | Revenue amount in USD                                                                           |
| `amount_tax`          | Tax amount collected                                                                            |
| `amount_tax_usd`      | Tax amount in USD                                                                               |
| `behavior`            | `exclusive` or `inclusive`                                                                      |
| `status`              | Tax lifecycle status (see below)                                                                |
| `error`               | Error details when `status` is `failed` (see [When calculation fails](#when-calculation-fails)) |

### Tax status lifecycle

| Status       | Description                                                            |
| :----------- | :--------------------------------------------------------------------- |
| `calculated` | Tax calculated during authorization, not yet reported to provider      |
| `submitted`  | Payment settled, tax transaction reported to provider                  |
| `reversed`   | Payment refunded, tax transaction reversed with provider               |
| `failed`     | Calculation or transaction submission failed (see `error` for details) |

### Amount relationships

```
payment.amount = tax.amount_subtotal + tax.amount_tax
```

| Field                 | Description               |
| :-------------------- | :------------------------ |
| `amount`              | Total charged to customer |
| `tax.amount_subtotal` | Revenue portion (pre-tax) |
| `tax.amount_tax`      | Tax collected             |

### When calculation fails

If the tax provider doesn't respond within 1.5 seconds or returns an error, PayNext processes the payment without tax to ensure you don't lose revenue. The `status` is `failed` and an `error` object contains the failure reason.

Failed transactions appear in **Dashboard → Tax → Failed** where you can review and submit them manually to your tax provider.

| Error code              | Description                                                                                           |
| :---------------------- | :---------------------------------------------------------------------------------------------------- |
| `timeout`               | Tax provider did not respond within 1.5 seconds                                                       |
| `calculation_failed`    | Tax provider request failed (see `message` for details)                                               |
| Provider-specific codes | Tax provider error types are mapped directly (e.g., `INVALID_CURRENCY_CODE`, `CALCULATION_NOT_FOUND`) |

<Accordion title="Example: Timeout error">
  ```json theme={"system"}
  "tax": {
    "provider": {
      "id": "b2c3d4e5-6789-01ab-cdef-2345678901bc",
      "type": "NUMERAL"
    },
    "calculation_id": null,
    "transaction_id": null,
    "error": {
      "code": "timeout",
      "message": "calculation timeout"
    },
    "amount_subtotal": 10000,
    "amount_subtotal_usd": 10000,
    "amount_tax": 0,
    "amount_tax_usd": 0,
    "behavior": "exclusive",
    "status": "failed"
  }
  ```
</Accordion>

<Accordion title="Example: Provider error">
  ```json theme={"system"}
  "tax": {
    "provider": {
      "id": "b2c3d4e5-6789-01ab-cdef-2345678901bc",
      "type": "NUMERAL"
    },
    "calculation_id": null,
    "transaction_id": null,
    "error": {
      "code": "INVALID_CURRENCY_CODE",
      "message": "Invalid customer currency code. Must be one of: EUR, RON, PLN, DKK, ..."
    },
    "amount_subtotal": 10000,
    "amount_subtotal_usd": 10000,
    "amount_tax": 0,
    "amount_tax_usd": 0,
    "behavior": "exclusive",
    "status": "failed"
  }
  ```
</Accordion>

<Accordion title="Example: Post-capture transaction failure">
  When tax calculation succeeds during authorization but the transaction submission fails after capture, the `status` becomes `failed` while preserving the original calculation amounts:

  ```json theme={"system"}
  "tax": {
    "provider": {
      "id": "b2c3d4e5-6789-01ab-cdef-2345678901bc",
      "type": "NUMERAL"
    },
    "calculation_id": "calc_xxx",
    "transaction_id": null,
    "error": {
      "code": "CALCULATION_NOT_FOUND",
      "message": "Unable to find calculation with ID: calc_xxx in test environment."
    },
    "amount_subtotal": 10000,
    "amount_subtotal_usd": 10000,
    "amount_tax": 700,
    "amount_tax_usd": 700,
    "behavior": "exclusive",
    "status": "failed"
  }
  ```
</Accordion>

<Note>
  Tax calculation failures are rare. When they occur, absorbing the tax cost is preferable to declining payments and losing revenue.
</Note>

## Webhooks

PayNext sends tax data in the `tax` object of payment webhook payloads. The `tax` object is included in:

* `payment_v2.created` — Initial payment with tax `status: "calculated"` (or `"failed"` if calculation errored)
* `payment_v2.settled` — After settlement, tax `status: "submitted"`
* `payment_v2.failed` — After capture failure, tax `status: "failed"`
* `payment_v2.refund.created` / `payment_v2.refund.settled` — Tax `status: "reversed"` when a refund succeeds

## Best practices

<AccordionGroup>
  <Accordion title="Use exclusive pricing for flexibility">
    Set your global default to `exclusive`. This lets you display pre-tax prices and add tax at checkout—the standard approach for US and B2B sales. Override to `inclusive` only for specific markets (like EU B2C) where tax-inclusive pricing is expected.
  </Accordion>

  <Accordion title="Start collecting before you register">
    You can start calculating and collecting tax before you're officially registered in a jurisdiction. Once registered, most tax providers support backfilling historical transactions. PayNext returns \$0 tax for customers in locations where you haven't enabled tax collection in **Integrations**.
  </Accordion>

  <Accordion title="Use USD amounts for reporting">
    For reporting convenience, use the `_usd` fields (`amount_subtotal_usd`, `amount_tax_usd`) to avoid currency conversion complexity. Your final tax liability is available in your tax provider's dashboard (e.g., Numeral)—use that information for your accounting and filing.
  </Accordion>
</AccordionGroup>
