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

# Preview billing calculations

## Overview

When a consumer changes product tier during a billing period, Bango DVM™ calculates a **pro-rated charge** or **pro-rated refund**. The `billingPreview` API lets you retrieve that projected adjustment **before the consumer confirms the change**.

Use the preview to show the immediate financial impact in your app or website. The consumer can then decide whether to proceed, and you submit the actual tier change separately.

> [!NOTE]
>
> Billing preview is available where DVM provides the optional **Billing & Charging** capability for a Consumer Offer with a fully active billing plan and outbound charges enabled. The initial supported change type is `PRODUCT_KEY_CHANGE` covering subscription upgrades and downgrades.

***

## Why preview a billing calculation?

The existing tier-change flow applies the change and then calculates its financial impact. Adding a preview gives you DVM’s calculation for your confirmation screen without reproducing the pro-ration logic in your own system.

> **Preview an upgrade**
>
> Show the projected immediate charge before the consumer moves to a higher-priced tier.

> **Preview a downgrade**
>
> Show the projected immediate refund before the consumer moves to a lower-priced tier.

A valid preview can also return a **zero adjustment**. A failed calculation must not be treated as zero.

> [!NOTE]
>
> Previewing a change does not update the Consumer Offer or Entitlement, create an invoice, or trigger a charge or refund. If the consumer abandons the journey, the preview has no billing or entitlement side effects.

***

## How it works

**1. Select the target tier**

Capture the product the consumer wants to change and the destination `productKey`.

**2. Request a billing preview**

Send the `consumerOfferId`, `productId`, and `targetProductKey` to the billing preview endpoint. DVM resolves the current product key and calculates the immediate adjustment.

**3. Display the financial impact**

Show the projected charge, refund, or zero adjustment and obtain the consumer’s confirmation.

**4. Check the preview is current**

Obtain a fresh preview if it has expired or a relevant product, offer, or billing change has occurred. Display the updated amount before proceeding.

**5. Submit the tier change**

After confirmation, use your existing [upgrade or downgrade flow](/entitlement-management/upgrade-an-entitlement). For the previewed amount to remain accurate, DVM must action the same change before `expiresAt`, with unchanged billing state.

***

## API request

```text theme={null}
POST /consumerOffers/{consumerOfferId}/billingPreview
```

Use your existing Consumer Offers API base URL and authenticate with a valid JWT. Missing or invalid JWTs return `401 Unauthorized`.

```json theme={null}
{
  "changeType": "PRODUCT_KEY_CHANGE",
  "productKeyChanges": [
    {
      "productId": "a212856a-4fd5-490b-a79c-f6dc950c2497",
      "targetProductKey": "product_key_123"
    }
  ]
}
```

> [!NOTE]
>
> `productKeyChanges` is an array containing **exactly one item**. Empty arrays or arrays containing more than one item return `400`. Both `consumerOfferId` and `productId` must be valid UUIDv4 identifiers, and `targetProductKey` is required.

| Field | Description |
| - | - |
| `consumerOfferId` (path) | The existing Consumer Offer for which you want to preview a change; must be a UUIDv4 |
| `changeType` | Use `PRODUCT_KEY_CHANGE` for an upgrade or downgrade preview |
| `productKeyChanges` | An array containing exactly one proposed product-key change |
| `productId` | The product within the Consumer Offer; must be a UUIDv4 and identify the intended product uniquely |
| `targetProductKey` | The destination configuration, which must be valid for the selected product and change |

DVM retrieves the current product key from its own state. You do not supply the starting key.

***

## API response

A successful preview returns `200 OK` with the calculation result and its expiry.

```json theme={null}
{
  "generatedAt": "2026-09-30T10:00:00.000Z",
  "expiresAt": "2026-09-30T10:15:00.000Z",
  "productKeyChanges": [
    {
      "productId": "a212856a-4fd5-490b-a79c-f6dc950c2497",
      "currentProductKey": "product_key_789",
      "targetProductKey": "product_key_123",
      "proratedAdjustment": {
        "amount": 2570,
        "currency": "EUR"
      }
    }
  ]
}
```

Identifiers, amount, and timestamps are illustrative. The 15-minute interval in this example is **not a fixed validity period**.

| Field | Description |
| - | - |
| `generatedAt` | When the preview was generated, expressed in UTC |
| `expiresAt` | The next daily granularity boundary in the lifecycle DVM maintains; refresh at or after this time |
| `productKeyChanges[].productId` | The product used for the calculation |
| `currentProductKey` | The starting configuration used by DVM |
| `targetProductKey` | The proposed destination configuration |
| `proratedAdjustment.amount` | The projected immediate financial adjustment |
| `proratedAdjustment.currency` | The currency of the adjustment |

> [!WARNING]
>
> **Draft — amount conventions to confirm:** Add the amount units and currency scaling, and define how the sign identifies a charge or refund. Add confirmed charge and refund examples before publishing. Do not assume that `2570` represents a particular display amount until these conventions are confirmed.

***

## Daily granularity and expiry

Production pro-ration uses **daily granularity** based on the lifecycle DVM maintains for the Consumer Offer. The remaining day count changes at the next lifecycle day boundary, which is reflected in `expiresAt`.

For the same intended tier change and unchanged billing state, the preview should match the actual adjustment if DVM actions the tier change **before expiry**.

| Timing | What you should do |
| - | - |
| Before `expiresAt`, with unchanged billing state | Proceed after consumer confirmation, allowing the change to be actioned before expiry |
| At or after `expiresAt` | Request a fresh preview and show the updated amount before proceeding |
| After a relevant product, offer, or billing change | Refresh the preview even if its expiry has not yet been reached |

> [!WARNING]
>
> Sending the tier-change request before expiry is sufficient only if DVM also **actions the change before that boundary**. A preview does not reserve a price or lock the billing state.

Use the returned timestamp rather than assuming the preview expires at calendar midnight or a fixed interval after generation. Convert UTC timestamps to the consumer’s local timezone where you display them.

For the day-counting rules used by upgrades and downgrades, see [Pro-ration](/billing-and-charging/pro-ration).

***

## Response codes and error handling

Error responses contain `statusCode` and `message`. Values in angle brackets in the table are replaced with the relevant identifiers or state.

**Error Response Table**

| HTTP status | Scenario | Response body |
| :- | :- | :- |
| `401` | Missing / invalid JWT | `{ "statusCode": 401, "message": "Unauthorized" }` |
| `400` | consumerOfferId not a uuid4 | `{ "statusCode": 400, "message": "ConsumerOfferId must be a valid version 4 UUID" }` |
| `400` | changeType missing / not PRODUCT\_KEY\_CHANGE | `{ "statusCode": 400, "message": "ChangeType must be one of [PRODUCT_KEY_CHANGE]" }` |
| `400` | productKeyChanges empty | `{ "statusCode": 400, "message": "ProductKeyChanges must contain at least 1 item" }` |
| `400` | productKeyChanges has more than 1 item | `{ "statusCode": 400, "message": "ProductKeyChanges must contain at most 1 item" }` |
| `400` | productId missing / not uuid4 | `{ "statusCode": 400, "message": "ProductId must be a valid version 4 UUID" }` |
| `400` | targetProductKey missing | `{ "statusCode": 400, "message": "TargetProductKey is a required field" }` |
| `404` | Consumer offer not found | `{ "statusCode": 404, "message": "consumer offer for <consumerOfferId> not found" }` |
| `404` | Consumer offer has no billing plan | `{ "statusCode": 404, "message": "no billing plan found for consumerOfferId <consumerOfferId>" }` |
| `404` | Billing plan not found | `{ "statusCode": 404, "message": "billing plan with id '<billingPlanId>' not found" }` |
| `404` | Billing plan event stream not found | `{ "statusCode": 404, "message": "<event stream not found message>" }` |
| `409` | Billing plan not fully active | `{ "statusCode": 409, "message": "cannot preview a tier change: billing plan is not fully active, billing plan '<billingPlanId>' in (state: <STATE>, subState: <SUBSTATE>)" }` |
| `409` | Billing suppressed (outbound charges disabled) | `{ "statusCode": 409, "message": "billing is suppressed for billing plan '<billingPlanId>' (outbound charges disabled): cannot preview a tier change" }` |
| `409` | Invalid tier change (target tier == current) | `{ "statusCode": 409, "message": "invalid tier change: entitlement '<entitlementId>' is already on product tier '<productTierKey>' (<productTierId>)" }` |
| `500` | Internal / downstream error | `{ "statusCode": 500, "message": "Internal server error" }` |

### Error-response example

```json theme={null}
{
  "statusCode": 400,
  "message": "ProductKeyChanges must contain at most 1 item"
}
```

### How to handle failures

* **400** — Correct the request before retrying.
* **401** — Check the JWT and authenticate again where required.
* **404** — Verify the Consumer Offer belongs to your namespace and has a billing plan. Contact Bango if an expected plan or its billing state cannot be found.
* **409** — Resolve the billing-plan state or suppressed billing. If the target tier is the current tier, investigate productKey misalignment.
* **500** — Retry transient failures; contact Bango if persistent. No retry schedule or retry header is specified for this endpoint.

***

## What’s next?

> **[Pro-ration](/billing-and-charging/pro-ration)**
>
> Understand how daily granularity affects upgrade charges and downgrade refunds.

> **[Upgrade or downgrade an offer](/entitlement-management/upgrade-an-entitlement)**
>
> Apply the tier change after the consumer confirms the previewed financial impact.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.