Send money
Pay a typed recipient, manage Commitments, and read the payment ledger.
This guide covers paying a typed recipient, managing a Commitment, and reading payments.
Before you start
- Auth and onboarding are done (Build the auth flow)
and
useCapxulIdentity()is authenticated withaccount.at === "claimed". - The account holds test funds (see test keys and the faucet).
- Skim Money & accounts for the model: Money, Account, Payment, Commitment.
- Check Capability status. Each payments claim uses the repository's fixed evidence state and names whether proof stops at a release-trunk contract, persistence, integration, or live execution boundary.
Amounts everywhere are a PaymentMoney: currency, decimal string value,
and decimals — for example { currency: "USD", value: "25.00", decimals: 6 }.
Never minor units, never a token amount. The current alpha validates payment
amounts at six-decimal precision — pass decimals: 6; other values are rejected with
INVALID_INPUT.
Pay a typed recipient
useCapxulPay sends money to a
typed recipient reference — a handle, an email, an organization handle, or
a saved payee id:
"use client";
import { useCapxulPay } from "@capxul/sdk-react";
export function PayButton() {
const pay = useCapxulPay();
const handlePay = async () => {
const payment = await pay.mutateAsync({
to: { kind: "email", email: "dana@example.com" },
amount: { currency: "USD", value: "25.00", decimals: 6 },
});
// Check payment.status: an instant send to a validated recipient
// settles; an unvalidated recipient escrows as "pending_claim".
};
return (
<button type="button" onClick={() => void handlePay()} disabled={pay.isPending}>
{pay.isPending ? "Sending…" : "Pay $25"}
</button>
);
}The to reference is one of:
kind | Fields | Pays |
|---|---|---|
"handle" | handle | a Capxul user by handle |
"email" | email | anyone by email — including people not on Capxul yet |
"organization" | handle | an org by its handle |
"payee" | id | a saved payee |
Two constraints to design around (both by design, both graded on Capability status):
- Bare addresses are rejected.
paynever accepts a raw EVM address. That path fails withINVALID_RECIPIENT. payis personal-actor only today. Passing an organizationactorfails withNOT_IMPLEMENTED— org treasury spending goes through the org surface, notpayments.pay.
Optionally attach a paymentType ("invoice", "payroll",
"reimbursement"), a payment document, or a timing clause. Omitting
timing means an instant send; a scheduled or stream clause always
creates a Commitment (below).
Unvalidated recipients become Commitments
One rule explains most "why is this payment not settled?" moments: an instant, irreversible send is only permitted to a validated recipient. Paying anyone not yet validated — a fresh email, an unclaimed handle — does not fail. It escrows the money in a recoverable Commitment instead:
- The
Paymentcomes back withstatus: "pending_claim". - The recipient claims it once they are on Capxul;
availableToClaimon the payment tracks what they can take. - Until claimed, the sender can cancel (reclaiming the unvested remainder) or redirect it to another validated recipient.
The SDK enforces this; it is not a bug to work around. The full model is in Money & accounts.
List payments and read one
useCapxulPayments lists the
payment ledger (most recent first);
useCapxulPayment reads one by id
with its live release state:
"use client";
import { useCapxulPayment, useCapxulPayments } from "@capxul/sdk-react";
function PaymentsList() {
const payments = useCapxulPayments();
if (payments.isLoading) return <p>Loading payments…</p>;
if (payments.isError) return <p>{payments.error.message}</p>;
return (
<ul>
{payments.data?.map((payment) => (
<li key={payment.id}>
{payment.recipient.label} · {payment.amount.value} {payment.amount.currency} ·{" "}
{payment.status}
</li>
))}
</ul>
);
}
function PaymentDetail({ paymentId }: { paymentId: string }) {
const payment = useCapxulPayment(paymentId);
if (payment.data == null) return null;
return (
<p>
Released {payment.data.released.value} / {payment.data.amount.value} — claimable:{" "}
{payment.data.availableToClaim.value}
</p>
);
}A Payment is leak-safe by construction: recipient kind + label, amount,
status (pending, settling, pending_claim, scheduled, streaming,
settled, cancelled, failed), timing,
released / availableToClaim, and document references. There is no
transaction hash and no address on it.
Money mutations invalidate the payments list and detail automatically — see TanStack Query integration.
Cancel a payment
Commitments are cancellable by their creator before they are fully claimed: cancelling returns the unvested remainder to you, while anything already vested stays claimable by the recipient.
Use client.payments.cancel(paymentId) in the Core SDK. React clients can use
useCapxulCancelPayment().
What you end up with
- A pay flow that sends to handles, emails, orgs, and payees — and degrades safely to escrowed Commitments for recipients who are not validated yet.
- A payments screen listing the ledger and showing per-payment release state.
- Claim, cancel, and redirect controls for recoverable Commitments.
Work with organizations
List the orgs a user belongs to, create one, switch the acting entity, read treasury/members/roles, and invite a member with a role.
TanStack Query integration
How the SDK rides TanStack Query — the provider-owned QueryClient, the key namespace, automatic invalidation, manual invalidation, and what clears on sign-out and re-bootstrap.