View Categories

Booking Deposits & Balances

10 min read

A booking deposit lets a customer pay part of the rental price at checkout and the rest, the balance, later: from a payment link, charged to their saved card, or at pick-up. This page covers the GraphQL fields that go with it: showing the offer in a cart, letting the shopper choose, reading a booking’s payment schedule, paying the balance from a headless storefront, and the back-office operations that list, collect, waive and charge balances.

The booking deposit is part of the rental price. It is not the refundable security deposit, which the Waiver & Damage add-on holds separately.

Booking deposits are off until the store turns them on under Stores > Configuration > Sales Igniter > Rental > Booking deposit. While they are off, Cart.rental_payment is null and no balances exist. Everything here is GraphQL; there is no other API for it.

Two halves #

HalfFieldsWho can call it
Storefront (customer)Cart.rental_payment
setRentalPaymentChoice
CustomerOrder.rental_payment
startRentalBalancePayment
A guest or customer token for the cart fields. The order field and startRentalBalancePayment need the customer token of the customer who placed the booking.
Back officerentalBalances
rentalBalance
RentalOrder.payment
sendRentalBalanceInvoice, markRentalBalancePaid
waiveRentalBalance, chargeRentalBalance
An admin or integration token holding the ACL resources in the table below.

Permissions #

FieldACL resourceWhere that is in the admin role tree
rentalBalances, rentalBalance, payment on rentalOrdersSalesIgniter_Rental::balanceRental > Booking balances: view
sendRentalBalanceInvoice, markRentalBalancePaid, waiveRentalBalance, chargeRentalBalanceSalesIgniter_Rental::balance_manageRental > Booking balances: record, send link, charge, waive

balance_manage sits under balance in the role tree, so a role that can manage balances can also view them. A token without the right resource gets a graphql-authorization error. RentalOrder.payment is the one exception: it returns null instead of an error when the token lacks SalesIgniter_Rental::balance, so an existing rentalOrders query keeps working for a role that cannot see balances.

The offer on a cart #

Ask the cart how it can be paid with rental_payment. It is null when booking deposits are off for the store. CartPrices.grand_total always equals due_today for the current choice, so a storefront that shows the cart’s grand total shows the right amount to pay now.

query CartPayment {
  cart(cart_id: "Zk3x9QJ0example") {
    prices { grand_total { value currency } }
    rental_payment {
      offered
      choice
      always
      full_total { value currency }
      due_today { value currency }
      due_later { value currency }
      due_at
      due_label
      collect
      auto_ok
      consent
      label_full
      label_part
      reason
      message
    }
  }
}
{
  "data": {
    "cart": {
      "prices": { "grand_total": { "value": 100, "currency": "USD" } },
      "rental_payment": {
        "offered": true,
        "choice": "PART",
        "always": false,
        "full_total": { "value": 400, "currency": "USD" },
        "due_today": { "value": 100, "currency": "USD" },
        "due_later": { "value": 300, "currency": "USD" },
        "due_at": "2026-10-23 09:00:00",
        "due_label": "Fri 23 Oct",
        "collect": "LINK",
        "auto_ok": false,
        "consent": null,
        "label_full": "Pay in full ($400.00)",
        "label_part": "Pay 25% now ($100.00), $300.00 on Fri 23 Oct",
        "reason": null,
        "message": null
      }
    }
  }
}

The wording of label_full, label_part, consent and message is the store’s own and follows the store view’s language; the example text above is only an illustration.

RentalCartPayment #

FieldTypeMeaning
offeredBoolean!True when this cart can be paid with a booking deposit. The shopper may still pay in full.
choiceRentalPaymentChoice!FULL or PART: what the cart is set to now.
alwaysBoolean!True when the store’s Who chooses setting is Always take a deposit, so there is no choice to show.
full_totalMoney!The order total, paid in full.
due_todayMoney!What the shopper pays at checkout with the current choice.
due_laterMoney!The balance: the rental share, its tax and, when the store says so, shipping. Zero when not offered.
due_atStringWhen the balance is due, store time, Y-m-d H:i:s.
due_labelStringThe due date as the cart shows it, such as Tue 29 Sep.
collectRentalBalanceCollectHow the balance will be collected.
auto_okBoolean!True when the balance will be charged to the card used at checkout. The shopper agrees to that with the consent sentence.
consentStringThe consent sentence to show beside the choice when auto_ok is true.
label_fullStringThe pay-in-full option, with its amount.
label_partStringThe deposit option: today’s amount, the balance and its date.
reasonStringWhy no deposit is offered: off, multishipping, extension, no_rental_rows, free, below_minimum, starts_soon, no_balance, payment_method, engine_missing, error.
messageStringA sentence for the shopper when the reason deserves one, such as a rental that starts too soon.

Only rental rows that qualify are split. Other rows in the cart, a buy-out line for example, are paid in full today. Rental extensions and subscription rentals are never split.

Letting the shopper choose #

setRentalPaymentChoice sets the cart to pay in full or to pay a deposit now. It returns the whole cart, so ask for prices and rental_payment in the same call and redraw. With the store’s Always take a deposit setting the choice is ignored: the cart stays on PART whenever a deposit is offered.

mutation Choose {
  setRentalPaymentChoice(input: { cart_id: "Zk3x9QJ0example", choice: FULL }) {
    cart {
      prices { grand_total { value currency } }
      rental_payment { choice due_today { value currency } due_later { value currency } }
    }
  }
}
{
  "data": {
    "setRentalPaymentChoice": {
      "cart": {
        "prices": { "grand_total": { "value": 400, "currency": "USD" } },
        "rental_payment": {
          "choice": "FULL",
          "due_today": { "value": 400, "currency": "USD" },
          "due_later": { "value": 0, "currency": "USD" }
        }
      }
    }
  }
}

The rest of checkout is unchanged: setShippingAddressesOnCart, setPaymentMethodOnCart and placeOrder work as normal, and the order is placed for the amount due today. A missing cart_id or choice is a graphql-input error.

TypeFields
SetRentalPaymentChoiceInputcart_id: String! (the masked cart id), choice: RentalPaymentChoice!
SetRentalPaymentChoiceOutputcart: Cart!

A booking’s payment schedule #

CustomerOrder.rental_payment gives a signed-in customer the same schedule My Account shows. It is null for an order paid in full and for any order that is not the caller’s own.

query MyBookingPayment {
  customer {
    orders(pageSize: 5) {
      items {
        number
        rental_payment {
          paid_today { value currency }
          security_deposit { value currency }
          balance { value currency }
          due_at
          due_label
          status
          collect
          message
          can_pay
          pay_url
        }
      }
    }
  }
}
{
  "data": {
    "customer": {
      "orders": {
        "items": [
          {
            "number": "000000123",
            "rental_payment": {
              "paid_today": { "value": 100, "currency": "USD" },
              "security_deposit": { "value": 50, "currency": "USD" },
              "balance": { "value": 300, "currency": "USD" },
              "due_at": "2026-10-23 09:00:00",
              "due_label": "Fri 23 Oct",
              "status": "SCHEDULED",
              "collect": "LINK",
              "message": "The balance is due on Fri 23 Oct.",
              "can_pay": true,
              "pay_url": "https://your-store.com/salesigniter_rental/balance/pay?token=..."
            }
          }
        ]
      }
    }
  }
}
FieldTypeMeaning
paid_todayMoney!What was paid at checkout, the security deposit left out.
security_depositMoneyThe refundable security deposit paid at checkout, when the booking has one.
balanceMoney!The balance, its tax share included.
due_atStringWhen the balance is due, store time.
due_labelStringThe due date as My Account shows it.
statusRentalBalanceStatus!Where the balance stands (see the status table).
collectRentalBalanceCollect!How the balance is collected.
messageStringOne sentence about where the balance stands.
can_payBoolean!True when the customer can pay the balance now, also before the due date.
pay_urlStringA signed payment link, valid 14 days. It is made when you ask for the field, so leave it out of queries that do not need it. null when there is nothing to pay.

Paying the balance from a headless storefront #

startRentalBalancePayment builds a cart that holds the balance and makes it the signed-in customer’s cart. Send the customer through your normal checkout with the returned cart_id; the balance is a separate order placed through the standard checkout, so every payment method’s own steps (3-D Secure, wallets, redirects) work unchanged. The customer’s previous cart comes back once the balance order is placed. It is allowed at any time the balance is owed, before or after the due date.

mutation PayBalance {
  startRentalBalancePayment(order_number: "000000123") {
    cart_id
  }
}
{ "data": { "startRentalBalancePayment": { "cart_id": "Qm7aBalanceCartExample" } } }

It needs the customer token of the customer who placed the booking. Errors:

CategoryWhen
graphql-authorizationNo customer token, or the order belongs to someone else.
graphql-no-such-entityNo such order for this customer.
graphql-inputThe order number is empty, the balance is no longer owed, or a payment for it is already being processed.

Guests have no account to sign in to. They pay from the emailed payment link instead.

Back-office reads #

rentalBalances #

Lists balances, soonest due first, like Rental > Send and Return > Balances in the admin. Requires SalesIgniter_Rental::balance. Every filter is optional.

query OverdueBalances {
  rentalBalances(filter: { queue: OVERDUE }, pageSize: 20, currentPage: 1) {
    total_count
    items {
      id
      order_number
      customer_name
      customer_email
      currency
      amount
      due_local
      collect
      status
      queue
      attempts
      next_attempt_at
      last_error
      rental_start
    }
    page_info { page_size current_page total_pages }
  }
}
{
  "data": {
    "rentalBalances": {
      "total_count": 1,
      "items": [
        {
          "id": 41,
          "order_number": "000000123",
          "customer_name": "Alex Example",
          "customer_email": "[email protected]",
          "currency": "USD",
          "amount": 300,
          "due_local": "2026-10-23 09:00:00",
          "collect": "AUTO",
          "status": "OVERDUE",
          "queue": "OVERDUE",
          "attempts": 3,
          "next_attempt_at": null,
          "last_error": "The card was declined.",
          "rental_start": "2026-10-30 10:00:00"
        }
      ],
      "page_info": { "page_size": 20, "current_page": 1, "total_pages": 1 }
    }
  }
}
ArgumentTypeNotes
filterRentalBalanceFilterInputSee below.
pageSizeIntDefault 20, at most 300.
currentPageIntDefault 1, 1-based.
Filter fieldTypeFinds
queueRentalBalanceQueueThe balances in one of the Balances screen’s quick filters.
status[RentalBalanceStatus!]Balances in any of these statuses.
collectRentalBalanceCollectBalances collected this way.
due_from, due_toStringDue on or after, and on or before, this UTC day, YYYY-MM-DD.
order_numberStringOne booking.
customer_emailStringOne customer.

rentalBalance #

The balance of one booking, found by its order number. Returns null when the booking was paid in full at checkout. Requires SalesIgniter_Rental::balance.

query OneBalance {
  rentalBalance(order_number: "000000123") {
    id order_id order_number amount base_amount tax currency
    due_at due_local collect status queue
    paid_via paid_at balance_order_id
  }
}

RentalOrder.payment returns the same RentalBalance on a rentalOrders result, so a picking list can show who still owes money. See Retrieving Orders by Rental Date.

RentalBalance #

FieldTypeMeaning
idInt!The balance’s id.
order_idInt!The booking order’s entity id.
order_numberString!The booking order’s number.
customer_name, customer_emailStringThe customer.
currencyStringThe order currency the balance is owed in.
amountFloat!The balance in the order currency, its tax share included.
base_amountFloat!The balance in the base currency.
taxFloat!The tax inside amount.
due_atStringWhen the balance is due, UTC.
due_localStringWhen the balance is due, store time.
collectRentalBalanceCollect!How it is collected.
statusRentalBalanceStatus!Where it stands.
queueRentalBalanceQueue!The Balances screen quick filter the row is in.
attemptsInt!Automatic charge attempts so far.
next_attempt_atStringThe next automatic attempt, UTC, after a soft decline.
last_errorStringThe last failure, as staff see it.
paid_viaStringonline, auto, counter_cash, counter_card, counter_bank, counter_other or waived.
paid_atStringUTC, Y-m-d H:i:s.
balance_order_idIntThe order that paid the balance, or the pending one waiting for an offline payment.
rental_start, rental_endStringThe earliest start and the latest end on the booking, store time.

Dates are Y-m-d H:i:s. Fields called due_at, paid_at and next_attempt_at are UTC; due_local, rental_start and rental_end are the store’s time. Money in RentalBalance is a plain number in the currency named by currency, not a Money object.

Back-office operations #

All four return the updated RentalBalance. They need SalesIgniter_Rental::balance_manage. A business-rule refusal (the balance is paid already, it is being collected right now, and so on) is a graphql-input error with a plain sentence in message.

MutationWhat it doesArguments
sendRentalBalanceInvoiceEmails the customer a signed payment link for the balance, valid 14 days.order_number: String!
markRentalBalancePaidRecords a balance paid outside the store (cash, card terminal, bank transfer). Places the balance order with the offline Paid at the counter method and invoices it.input: MarkRentalBalancePaidInput!
waiveRentalBalanceWrites the balance off. The booking keeps what was paid.input: WaiveRentalBalanceInput!
chargeRentalBalanceCharges the saved card now. Only for balances collected automatically. A decline is not an error: it comes back in status and last_error.order_number: String!
mutation Counter {
  markRentalBalancePaid(input: { order_number: "000000123", method: CASH, reference: "Receipt 4471" }) {
    order_number
    status
    paid_via
    paid_at
    balance_order_id
  }
}
{
  "data": {
    "markRentalBalancePaid": {
      "order_number": "000000123",
      "status": "PAID",
      "paid_via": "counter_cash",
      "paid_at": "2026-10-30 09:42:10",
      "balance_order_id": 987
    }
  }
}
mutation Waive {
  waiveRentalBalance(input: { order_number: "000000123", reason: "Goodwill after a late delivery" }) {
    order_number
    status
    paid_via
  }
}
mutation Charge {
  chargeRentalBalance(order_number: "000000123") {
    order_number
    status
    attempts
    next_attempt_at
    last_error
  }
}
InputFields
MarkRentalBalancePaidInputorder_number: String!, method: RentalCounterPaymentMethod!, reference: String (a receipt or transfer reference, kept on the balance order)
WaiveRentalBalanceInputorder_number: String!, reason: String (written into the order comment)

Refunds, including Refund booking, are not part of this API. Use the admin order screen for them.

Enumerations #

EnumValues
RentalPaymentChoiceFULL pay in full now; PART pay a booking deposit now and the balance later.
RentalBalanceCollectLINK the customer is emailed a payment link; AUTO charged to the saved card on the due date; PICKUP paid at the counter at pick-up.
RentalCounterPaymentMethodCASH, CARD (a card terminal), BANK (a bank transfer), OTHER.

RentalBalanceStatus #

ValueMeaning
SCHEDULEDThe deposit is paid; the balance is not due yet.
DUEDue now, collected by link or at pick-up.
CHARGINGAn automatic charge is running, or waiting for the payment gateway to confirm.
PAIDThe balance is paid.
FAILEDThe automatic charge was declined. See next_attempt_at for the next try.
NEEDS_ACTIONThe bank asked the customer to confirm the payment.
OVERDUEPast due and still unpaid.
WAIVEDWritten off by staff.
CANCELLEDThe booking was cancelled or refunded, so nothing is owed.
RELEASEDThe booking was released for non-payment.

RentalBalanceQueue #

The quick filters of the Balances screen. Each balance is in exactly one.

ValueMeaning
DUE_THIS_WEEKDue within the week.
OVERDUEOverdue.
FAILEDThe automatic charge failed.
WAITINGWaiting for the customer to confirm a payment.
SCHEDULEDNot due yet.
PAID_30_DAYSPaid in the last 30 days.
CLOSEDPaid earlier, waived, cancelled or released.

Errors #

CategoryWhen
graphql-authorizationThe token lacks the ACL resource, or there is no customer token where one is needed.
graphql-inputA missing or empty argument, an unknown method, or a balance that cannot take the action (for example it is already paid, or a charge is running).
graphql-no-such-entityThe order number does not exist.

For tokens, dates, paging and error shapes shared by every field, see GraphQL API Overview & Authentication.