Payments, fees & connectors
How money moves on the desk: the ways to pay, the monthly participation fee,
what happens when a payment is confirmed late, and how the operator switches
the Stripe and Coinbase Commerce connectors on and off. Machine-readable
summary: /api/v1/payments/methods.
Ways to pay
| Method | Provider | How it works |
|---|---|---|
| Account balance | – | Each order is debited from the trading account it is placed on (your Manual desk for lines you picked, an agent's account for lines it proposed). Top up a balance from the ledger page. |
| Card | Stripe | New card: you are sent to a hosted Stripe Checkout page; the card is stored (brand, last four digits, expiry, provider reference) for later payments. Saved card: charged off-session at once — this is also what an agent's execute grant uses. |
| Crypto | Coinbase Commerce | You are sent to a hosted Coinbase Commerce page and pay in BTC, ETH, USDC or another supported asset. The orders are funded when Coinbase Commerce confirms the payment (webhook), usually within minutes. |
Every payment form (cart page, cart checkout link, single-order purchase link, top-up) offers whichever of these are enabled on the deployment. One card or crypto payment covers every line in the carts being paid, even when the lines are ordered on different trading accounts.
Hosted payments. For Stripe Checkout and Coinbase Commerce the orders are
placed first and wait (awaiting_payment) while you pay on the provider's
page; https://t5market.com/pay/<token> shows the state of the payment and is where
you land when you return. If a session closes before the provider confirms
the payment, the order for that session expires; the money you paid for
it is credited to your account balance and the line goes back to your
cart, so nothing is lost and nothing is executed on stale terms. Cancelling
or failing a hosted payment removes the waiting orders and returns the lines
to the cart.
The monthly participation fee
A participation fee of 15.00 USD per calendar month is charged with the account holder's first transaction that funds orders in that month:
- it is added to that payment (card or crypto), or debited from the account balance when you pay from balance — the payment forms show the fee and the total before you pay;
- it is charged once per calendar month: further payments in the same month carry no fee, and the forms say so ("no fee on this payment");
- top-ups and payouts never carry the fee; a month in which you fund no orders costs nothing;
- an agent that executes a cart under a grant triggers the fee just like a
payment you make yourself — the API answer carries
feeandcharged; - the fee appears in the ledger as a
monthly_feeentry on the account the transaction ran on, and the console shows whether the current month is paid.
GET /api/v1/account reports monthly_fee.due for the account holder;
GET /api/v1/payments/methods states the amount and the rule.
Ledger vocabulary
card_charge (+, a card payment received), crypto_payment (+, a confirmed
crypto payment), order_debit (−), monthly_fee (−), sale_credit (+),
holding_fee (−), surcharge (−), salvage_credit (+, the part of unsold
inventory cost not written off — nothing with the default 100 % write-off),
payout (−). A card or crypto receipt
that includes the participation fee is booked as one credit followed by the
order debits and the fee debit, so the balance is unchanged by the receipt
itself.
Terms, privacy and acceptance
Opening an account requires accepting the terms and conditions
and the privacy policy; an account holder who registered under
an earlier version accepts the current one with their next payment. The
version in force is XGM_TERMS_VERSION (shown at the top of both pages and
in GET /api/v1/manifest → legal). The platform sets no tracking cookies —
only the session cookie that keeps you logged in.
Operating the connectors
Everything is configured through the environment (see Deploying):
| Variable | Default | Meaning |
|---|---|---|
XGM_PAYMENTS_MODE |
mock |
mock: no provider is contacted — test cards are accepted inline, the hosted Stripe/Coinbase pages are simulated by /pay/<token> with "simulate success / failure" buttons, and webhooks are still signature-checked with the mock secrets. live: real connectors. |
XGM_STRIPE_ENABLED |
1 |
Switch the card connector on or off. Off = no card option anywhere. |
XGM_STRIPE_SECRET_KEY |
– | Stripe secret key (sk_live_… / sk_test_…); required in live mode for the card option to appear. |
XGM_STRIPE_WEBHOOK_SECRET |
whsec_mock |
Signing secret of the webhook endpoint POST https://t5market.com/payments/stripe/webhook (events checkout.session.completed, checkout.session.expired, checkout.session.async_payment_*). |
XGM_COINBASE_ENABLED |
1 |
Switch the crypto connector on or off. |
XGM_COINBASE_API_KEY |
– | Coinbase Commerce API key; required in live mode for the crypto option to appear. |
XGM_COINBASE_WEBHOOK_SECRET |
cc_mock |
Shared secret of the webhook endpoint POST https://t5market.com/payments/coinbase/webhook (events charge:confirmed, charge:resolved, charge:failed). |
XGM_MONTHLY_FEE |
15 |
The participation fee (set 0 to disable it). |
XGM_CURRENCY |
USD |
Currency of prices, fees and payments. |
XGM_TERMS_VERSION |
2026-09-03 |
Bump when the terms or the privacy policy change; every holder accepts again at their next payment. |
A connector that is enabled but not configured in live mode is reported as
ready: false and its method disappears from the forms; the other methods
keep working. Switching Stripe on later requires nothing but the keys and a
restart: cards stored in mock mode are test cards and are refused in live
mode (the holder pays through Stripe Checkout once, which stores a real
payment method).
Live mode checklist. Set XGM_PAYMENTS_MODE=live; add the Stripe secret
key and register https://<domain>/payments/stripe/webhook in the Stripe
dashboard with the events above, copying its signing secret; add the
Coinbase Commerce API key and register
https://<domain>/payments/coinbase/webhook in the Commerce settings,
copying the shared secret. Returning holders are also served by polling: when
they come back from the provider before the webhook arrived, the platform
asks the provider for the state of the session or charge and completes the
payment if it is paid.
Testing locally
In mock mode (the default) everything can be exercised without keys:
- pay with the inline test card
4242 4242 4242 4242— an instant mock Stripe charge; - choose Card via Stripe Checkout or Crypto via Coinbase Commerce — you
land on
/pay/<token>, press Simulate a successful payment (orders funded, a mock Stripe card stored) or Simulate a failed payment (lines back in the cart); - post a signed webhook yourself, exactly as the provider would:
BODY='{"event":{"type":"charge:confirmed","data":{"code":"X","metadata":{"payment_token":"<token>"}}}}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac cc_mock | sed 's/^.* //')
curl -s -X POST https://t5market.com/payments/coinbase/webhook -H "X-CC-Webhook-Signature: $SIG" -H 'content-type: application/json' -d "$BODY"
The test-suite (tests/test_payments.py) covers the fee once per month,
both hosted flows, signed webhooks, late confirmation after a session close,
and the acceptance of terms.