Octopus Cards

Charges

Calculate exact pricing for a product before placing an order

POST /api/v1/products/:id/charges

Calculate the exact cost of an order before placing it. Returns the breakdown including face value, discount, FX conversion (if applicable), and the final amount that will be deducted from your wallet.

Always call this endpoint before creating an order. It gives you the exact amount that will be charged, including any FX conversion fees. The response is cached for 5 minutes per unique combination of product, denomination, quantity, and wallet.

Request

curl -X POST "{{host}}/api/v1/products/123/charges" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "denomination": 50.00,
    "quantity": 5,
    "wallet_id": 1
  }'
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

type ChargesRequest struct {
    Denomination float64 `json:"denomination"`
    Quantity     int     `json:"quantity"`
    WalletID     *int    `json:"wallet_id,omitempty"`
}

type ChargesDetails struct {
    SourceCurrency      string   `json:"source_currency"`
    DestinationCurrency string   `json:"destination_currency"`
    ForexRate           *float64 `json:"forex_rate"`
    ConversionFee       *float64 `json:"conversion_fee"`
}

type ChargesResponse struct {
    NonDiscountedTotal float64        `json:"non_discounted_total"`
    DiscountAmount     float64        `json:"discount_amount"`
    TotalAmount        float64        `json:"total_amount"`
    Discount           float64        `json:"discount"`
    GSTAmount          float64        `json:"gst_amount"`
    TotalPayable       float64        `json:"total_payable"`
    MaxQuantity        int            `json:"max_quantity"`
    NetAmount          *float64       `json:"net_amount"`
    HandlingFeeAmount  *float64       `json:"handling_fee_amount"`
    ChargesDetails     ChargesDetails `json:"charges_details"`
}

func main() {
    walletID := 1
    body, _ := json.Marshal(ChargesRequest{
        Denomination: 50.00,
        Quantity:     5,
        WalletID:     &walletID,
    })

    req, _ := http.NewRequest("POST", "{{host}}/api/v1/products/123/charges", bytes.NewReader(body))
    req.Header.Set("Authorization", "Bearer <token>")
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    var charges ChargesResponse
    json.NewDecoder(resp.Body).Decode(&charges)

    fmt.Printf("Subtotal:  %.2f\n", charges.NonDiscountedTotal)
    fmt.Printf("Discount:  -%.2f (%.1f%%)\n", charges.DiscountAmount, charges.Discount)
    fmt.Printf("Payable:   %.2f\n", charges.TotalPayable)
}

Request Parameters

KeyTypeRequiredDescription
idintegerYesProduct ID (path parameter)
denominationnumberYesFace value of the product. Must be between 0.01 and 1,000,000,000. Must fall within an available denomination range.
quantityintegerYesNumber of vouchers. Must be at least 1 and not exceed your client's bulk limit.
wallet_idintegerNoWallet to debit from. If omitted, the platform uses your wallet matching the product's currency. Required when paying with a different currency (triggers FX conversion).

Response

{
  "non_discounted_total": 250.00,
  "discount_amount": 8.75,
  "total_amount": 241.25,
  "discount": 3.5,
  "gst_amount": 0.00,
  "total_payable": 241.25,
  "max_quantity": 100,
  "net_amount": 241.25,
  "handling_fee_amount": 0.00,
  "charges_details": {
    "source_currency": "USD",
    "destination_currency": "USD",
    "forex_rate": null,
    "conversion_fee": null
  }
}

With FX conversion (e.g. paying from EUR wallet for a USD product):

{
  "non_discounted_total": 250.00,
  "discount_amount": 8.75,
  "total_amount": 241.25,
  "discount": 3.5,
  "gst_amount": 0.00,
  "total_payable": 222.81,
  "max_quantity": 100,
  "net_amount": 222.81,
  "handling_fee_amount": 0.50,
  "charges_details": {
    "source_currency": "EUR",
    "destination_currency": "USD",
    "forex_rate": 0.9210,
    "conversion_fee": 0.50
  }
}

Response Fields

KeyTypeDescription
non_discounted_totalnumberFace value × quantity (before discount)
discount_amountnumberTotal discount applied
total_amountnumberAmount after discount (before FX conversion)
discountnumberDiscount percentage applied
gst_amountnumberTax amount (if applicable)
total_payablenumberFinal amount deducted from your wallet (after FX + fees)
max_quantityintegerMaximum units you can order for this product
net_amountnumber or nullNet amount after all adjustments
handling_fee_amountnumber or nullHandling fee (if applicable)
charges_details.source_currencystringYour wallet's currency
charges_details.destination_currencystringThe product's currency
charges_details.forex_ratenumber or nullFX rate applied (null if same currency)
charges_details.conversion_feenumber or nullFX conversion fee (null if same currency)

Pricing Calculation

non_discounted_total  = denomination × quantity
discount_amount       = non_discounted_total × (discount / 100)
total_amount          = non_discounted_total − discount_amount
total_payable         = (total_amount × forex_rate) + conversion_fee + handling_fee

Errors

400 Bad Request — Product ID is not valid.

{
  "error": {
    "name": "BadRequestError",
    "code": "BAD_REQUEST",
    "message": "Invalid product ID"
  }
}

400 Bad Request — Denomination is outside the available range.

{
  "error": {
    "name": "BadRequestError",
    "code": "BAD_REQUEST",
    "message": "Denomination not available"
  }
}

Ensure the denomination falls within one of the product's available_denominations ranges.

400 Bad Request — Quantity exceeds your client's bulk limit.

{
  "error": {
    "name": "BadRequestError",
    "code": "BAD_REQUEST",
    "message": "Quantity exceeds maximum"
  }
}

Check the max_quantity field in the response to see your limit.

400 Bad Request — The specified wallet does not exist or you have no wallet in the product's currency.

{
  "error": {
    "name": "BadRequestError",
    "code": "BAD_REQUEST",
    "message": "Appropriate wallet not found"
  }
}

404 Not Found — Product does not exist or is blacklisted.

{
  "error": {
    "name": "NotFoundError",
    "code": "NOT_FOUND",
    "message": "Product not found"
  }
}

401 Unauthorized — Missing or invalid JWT token.

{
  "error": {
    "name": "UnauthorizedError",
    "code": "UNAUTHORIZED",
    "message": "Authorization header required"
  }
}

On this page