React SDKHooksPayments

useCapxulPay

Mutation hook for sending money to a validated recipient — instant by default, or a Commitment stream.

Sends money from the signed-in user's Account to a recipient — a handle, email, Organization, or saved payee. Read results back with useCapxulPayments.

Import

import { useCapxulPay } from "@capxul/sdk-react";

Usage

"use client";

import { useCapxulPay } from "@capxul/sdk-react";

function PayButton({ email }: { email: string }) {
  const pay = useCapxulPay();

  return (
    <div>
      <button
        disabled={pay.isPending}
        onClick={() =>
          pay.mutate({
            to: { kind: "email", email },
            amount: { currency: "USD", value: "25.00", decimals: 6 },
          })
        }
      >
        {pay.isPending ? "Sending…" : "Send $25"}
      </button>
      {pay.error ? <p>Payment failed ({pay.error.code}).</p> : null}
      {pay.data ? (
        <p>
          Payment {pay.data.id} is {pay.data.status}.
        </p>
      ) : null}
    </div>
  );
}

Parameters

The hook itself takes no parameters. The mutation takes a PaymentsPayInput:

Prop

Type

Pass the payment intent only. The hook creates and stores the request key. A retry after a page reload uses the same key. The hook retains the key while an attempt is unresolved. It removes the key after all attempts succeed. The hook fails before the backend request when persistent storage or Web Locks are unavailable.

to — the recipient. Recipients are typed references, never raw addresses. The accepted forms are:

  • { kind: "handle", handle } — a handle you have reserved for one of your payees (a leading @ is tolerated and stripped).
  • { kind: "email", email } — an email address.
  • { kind: "organization", handle } — an organization by its handle.
  • { kind: "payee", id } — a saved payee by id.

A bare EVM address (0x + 40 hex characters) is rejected by design with an INVALID_INPUT error. The SDK and the backend both enforce this rule. The TargetReference union has a destination variant, but pay rejects it with NOT_IMPLEMENTED.

actor. Only the personal actor can pay in the current alpha. Passing an organization actor fails with NOT_IMPLEMENTED (payments.pay.organizationActor). See Capability status.

timing. Omitted means instant — the direct-send path. A { kind: "stream", startsAt, endsAt, cliffAt? } clause turns the payment into a Commitment: an escrowed, cancellable, redirectable payment that the recipient claims as it vests. Timestamps are epoch milliseconds. Streams enter the ledger as scheduled, streaming, or pending_claim instead of pending.

Scheduled timing ({ kind: "scheduled", at }) returns INVALID_INPUT on timing with the reason scheduled payments are being rebuilt. Existing holds keep claim, cancel and redirect.

The instant-send guardrail

An instant, irreversible payment is only permitted to a validated recipient. Paying anyone not yet validated always goes through a recoverable Commitment (escrowed, cancellable, redirectable). The SDK enforces this; it is not a bug to work around. See Capability status.

document and supportingReceipts. Personal payments accept the SDK's PaymentDocumentOptions: a Memo draft, an Invoice draft, or a supplied V3 envelope with exact bytesBase64. Invoice line items belong inside that draft or envelope input; top-level lineItems is refused. An Invoice draft supplies invoiceNumber, payerRef, payeeRef, dueAt and lineItems. The backend binds its amount and target to the resolved payment and checks its item total. Without a document, it creates a Memo with empty reference and note.

Invoice requires paymentType: "invoice" when explicitly supplied. Memo keeps an explicit classification. Otherwise supporting Receipts imply reimbursement, and payments without Receipts default to unspecified. Payroll is reserved for Payroll runs. Supplied supporting Receipt bytes remain separate from the current payment instruction and do not change its amount.

Return type

import { type UseCapxulPayReturn } from "@capxul/sdk-react";
// UseMutationResult<Payment, CapxulError, PaymentsPayInput>

On success, data is the recorded Payment: its id, status, amount, the actor-relative direction, counterpartyLabel, nullable settled receipt, recipient, attached document references, timing, and commitment amounts.

The hook returns TanStack Query's UseMutationResult. The most-used fields:

FieldDescription
mutateFire the mutation (fire-and-forget; pair with onSuccess/onError callbacks).
mutateAsyncFire the mutation and get a Promise of the result. Rejects with a CapxulError on failure.
dataThe mutation result (typed per hook, shown above). undefined until the first success.
errorA CapxulError when the last attempt failed, otherwise null.
isPendingtrue while the mutation is in flight. Use it to disable submit buttons.
resetClear the mutation state (data, error) back to idle.

Mutations do not retry by default. The full field list is in the TanStack Query useMutation reference; see also the TanStack Query integration guide.

Client-backed mutation hooks are safe to render while CapxulProvider bootstraps. If one is fired before bootstrap finishes, mutateAsync rejects with a CapxulError whose code is WRONG_STATE; applications do not need to hide their route tree behind useCapxul(). Every shipped mutation hook is client-backed — alpha.23 removed the one client-free hook (useCapxulSwitchActingEntity, a documented no-op).

Cache behavior

On success this mutation refreshes the personal money state: the payments list (and payment detail queries under it), the personal account balance, the personal activity projection, and the personal address book. Paying someone new records them as a counterparty.

Errors

Failures surface as a CapxulError on error / thrown from mutateAsync. Notable modes: INVALID_INPUT for a raw 0x address, an empty or unresolvable recipient (unknown handle, payee, or org handle), or an invoice document that disagrees with the amount or line items; NOT_IMPLEMENTED for an organization actor or a destination target.

Client method

This hook wraps client.payments.pay(input) on the core SDK.

On this page