ptr_utilities

Automated Billing with CC: Tweaked + Numismatics

How to set up recurring, automated billing (rent, utilities, memberships, etc.) using the official Numismatics Bank Terminal peripheral — no custom mod required. Everything here works on a stock CC: Tweaked computer next to a Numismatics Bank Terminal.

Full API reference: NUMISMATICS_API.md (in this folder) A ready-made example implementation ships in auto_billing_example.lua (pure Lua, reference only).


The Core Idea

Numismatics authorized cards are the official automation mechanism. Each authorized card is bound to a sub-account of a player’s bank account. A sub-account has:

  • its own authorization UUID
  • an authorization type controlling who may spend it:
    • TRUSTED_PLAYERS — only players on its trust list
    • TRUSTED_AUTOMATION — trusted players and machines/automation
    • ANYBODY — anything that holds the card data
  • an optional spending limit (hard cap tracked by a spent counter)

A computer holding the card’s (accountID, authorizationID) pair can pull money from that sub-account via the Bank Terminal peripheral’s bank.transfer(...). Numismatics itself enforces authorization type and spend limit — your script can’t overspend even if it wants to.


Requirements

  • CC: Tweaked computer
  • Numismatics Bank Terminal placed adjacent to the computer
  • Numismatics config: getSubAccountsCommand = true if you want to list sub-account labels programmatically (optional but handy)

Hardware Setup

  1. Place a Bank Terminal next to the computer.
  2. Optional: a monitor for status display.

That’s it — the Bank Terminal peripheral handles all bank access. You do NOT need barrels or card readers for billing; the card is only needed once, to read out the account + authorization UUIDs (see below).


Subscriber Setup (what each player does)

  1. Open their Bank Terminal → SubAccounts tab
  2. Create a sub-account for the service (e.g. “Server Rent”)
  3. Set the authorization type to TRUSTED_AUTOMATION (recommended) or ANYBODY
  4. Optionally set a spending limit — this acts as a hard safety cap
  5. Create an authorized card bound to that sub-account
  6. Hand the card to whoever runs the billing computer

The biller reads the pair off the card once (right-clicking the card shows the IDs, or scan it through any system that exposes NBT) and stores:

{ accountID = "<player account uuid>", authorizationID = "<sub-account uuid>" }

Biller Setup (what you do)

  1. Wrap the terminal:
local bank = peripheral.find("Numismatics_BankTerminal")
  1. Store subscriber records in a config file (JSON via textutils).
  2. On your schedule, call transfer for each subscriber.

The Billing Call

-- Pull 100 cogs (6400 spurs) from a subscriber's sub-account to your account
local ok, err = pcall(bank.transfer,
    sub.accountID,          -- fromAccountID       (parent account)
    sub.authorizationID,    -- fromAuthorizationID (sub-account)
    myAccountID,            -- toAccountID         (your account)
    6400)                   -- amount in spurs

if not ok then
    print("Billing failed for " .. (sub.label or sub.accountID) .. ": " .. tostring(err))
end

Useful companion calls:

bank.getBalance(accountID)                                  -- balance in spurs
bank.getMaxAvailableWithdrawal(accountID, authID)           -- remaining quota
bank.getSubAccounts(accountID)                              -- list sub-auth UUIDs (*)
bank.getSubAccountLabel(accountID, authID)                  -- human-readable label (*)

(*) requires the getSubAccountsCommand config option.

Scheduling

CC: Tweaked computers don’t tick reliably across server restarts, so anchor billing to wall-clock time instead of uptime:

local now = os.epoch("utc") / 1000
if now - state.lastBilled >= 86400 then   -- 24h
    billAllSubscribers()
    state.lastBilled = now
end
os.sleep(30)

Persist state and the subscriber list to disk (textutils.serialiseJSON) so restarts don’t double-bill anyone.


How the Limit Works

The sub-account limit is a hard cap enforced by Numismatics, tracked by a monotonic spent counter:

  • Limit hit → transfer throws; the subscriber must reset/raise the limit in their Bank Terminal GUI (or set no limit)
  • No limit set → effectively capped by the account balance
  • Never reset limits from automation — that defeats their purpose

Check getMaxAvailableWithdrawal(accountID, authID) before billing to show “X remaining” style info instead of relying on failures.

Failure Handling

ErrorCauseWhat to do
Insufficient fundsBalance below amountRetry next cycle
Deduction / limit errorSub-account spent counter maxedSubscriber resets limit
Account not foundAccount deletedRemove subscriber
Authorization errorsSub-account deletedRe-issue card

Log failures per subscriber and keep going — one bad subscriber shouldn’t block the rest of the cycle.

Security Notes

  • Treat stored (accountID, authorizationID) pairs like cash — anyone who can run Lua on that computer can spend from them
  • Prefer TRUSTED_AUTOMATION over ANYBODY so leaked pairs can’t be used by arbitrary machines
  • Keep billing amounts at or below what subscribers agreed to; the limit is the real safety net, not your script