> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paywithatoa.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Collect — money in

> Collect money with Agent Pay: take a customer-present pay-link, or charge off-session under a COLLECT contract the customer approved once.

Two ways in. The customer is present and pays a link, or they're away and you charge under a **COLLECT contract**
they approved once. You never pass card or method details in either — Atoa charges the method the customer saved.

## Pay-link — customer present

```python Python theme={null}
req = atoa.payment.collect(
    amount={"amount": 45.00},    # pounds, not pence (45.00 = £45); GBP default
    order_id="order-1099",       # your own reference
)

print(req.payment_url)           # hand over the link
print(req.qr_code_url)           # or the QR

settled = atoa.payment.await_settled(req.payment_request_id)
print(settled.status)            # COMPLETED
```

```typescript TypeScript theme={null}
const req = await atoa.payment.collect({
  amount: { amount: 45.00 },     // pounds, not pence (45.00 = £45); GBP default
  orderId: "order-1099",         // your own reference
});

console.log(req.paymentUrl);     // hand over the link
console.log(req.qrCodeUrl);      // or the QR

const settled = await atoa.payment.awaitSettled(req.paymentRequestId);
console.log(settled.status);     // COMPLETED
```

* `expiresIn` (milliseconds) shortens the link's window; an unpaid link reads back `EXPIRED`.
* Cancel an unpaid link with `payment.cancel(paymentRequestId)` → `CANCELLED`, reason `MERCHANT_CANCELLED`.

**Sandbox:** open the link, choose the **Atoa Test Bank**, and pick the outcome — `COMPLETED`, `FAILED`, or
`PENDING`. Cancelling on the bank page gives `CUSTOMER_CANCELLED`.

## Off-session — under a COLLECT contract \[#off-session]

The customer approves the contract (and links a payment method) once at its `authorizationUrl`. After that you
charge under the contract while they're away — pass the `contractId`, never a payment method.

```python Python theme={null}
customer = atoa.customer.create(
    full_name="Ada Lovelace",
    email="ada@example.com",      # email or phone required
)

contract = atoa.contract.create(
    type="COLLECT",
    name="Monthly membership",
    atoa_customer_id=customer.id,
    limits={
        "max_per_payment": 50.00,
        "period_limits": [{"amount": 100.00, "period": "MONTH"}],
        "valid_to": "2026-12-31T23:59:59Z",
    },
)

print(contract.authorization_url)                # customer links a method + approves the caps here
atoa.contract.await_active(contract.contract_id)          # → ACTIVE

charge = atoa.payment.collect(
    contract_id=contract.contract_id,
    atoa_customer_id=customer.id,
    amount={"amount": 20.00},
    order_id="membership-jan",
)
# charge.next_action — the customer approves before it settles (next section)
```

```typescript TypeScript theme={null}
const customer = await atoa.customer.create({
  fullName: "Ada Lovelace",
  email: "ada@example.com",       // email or phone required
});

const contract = await atoa.contract.create({
  type: "COLLECT",
  name: "Monthly membership",
  atoaCustomerId: customer.id,
  limits: {
    maxPerPayment: 50.00,
    periodLimits: [{ amount: 100.00, period: "MONTH" }],
    validTo: "2026-12-31T23:59:59Z",
  },
});

console.log(contract.authorizationUrl);          // customer links a method + approves the caps here
await atoa.contract.awaitActive(contract.contractId);    // → ACTIVE

const charge = await atoa.payment.collect({
  contractId: contract.contractId,
  atoaCustomerId: customer.id,
  amount: { amount: 20.00 },
  orderId: "membership-jan",
});
// charge.nextAction — the customer approves before it settles (next section)
```

Atoa enforces the `limits` on every charge. Check what's left in the window before charging, rather than finding
out from a failed charge:

```python Python theme={null}
usage = atoa.contract.get(contract.contract_id).usage or []
month = next((u for u in usage if u.period == "MONTH"), None)
print(month.remaining)           # what is left this window; over-cap charges fail LIMIT_EXCEEDED
```

```typescript TypeScript theme={null}
const { usage } = await atoa.contract.get(contract.contractId);
const month = usage?.find((u) => u.period === "MONTH");
console.log(month?.remaining);   // what is left this window; over-cap charges fail LIMIT_EXCEEDED
```

## Charge on approval \[#charge-on-approval]

A COLLECT contract can bundle a **first charge into the customer's single approval** — one page both activates the
contract *and* takes that payment (a joining fee, a deposit, the first bill). Pass `initialCharge` to
`contract.create`; don't call `payment.collect` separately for charge #1.

```python Python theme={null}
contract = atoa.contract.create(
    type="COLLECT",
    name="Gym membership",
    atoa_customer_id=customer.id,
    limits={
        "max_per_payment": 50.00,
        "period_limits": [{"amount": 50.00, "period": "MONTH"}],
        "valid_to": "2027-01-31T23:59:59Z",
    },
    initial_charge={
        "amount": 25.00,             # ≥ £1 and ≤ max_per_payment
        "order_id": "joining-1001",  # your reference (≤ 50 chars)
        "notes": "Joining fee",      # optional (≤ 30 chars)
    },
)

# One approval at contract.authorization_url activates the contract AND takes the charge.
atoa.contract.await_active(contract.contract_id)
first = atoa.contract.get(contract.contract_id).initial_charge
print(first.status)                  # AWAITING_APPROVAL → PENDING → COMPLETED | FAILED
```

```typescript TypeScript theme={null}
const contract = await atoa.contract.create({
  type: "COLLECT",
  name: "Gym membership",
  atoaCustomerId: customer.id,
  limits: {
    maxPerPayment: 50.00,
    periodLimits: [{ amount: 50.00, period: "MONTH" }],
    validTo: "2027-01-31T23:59:59Z",
  },
  initialCharge: {
    amount: 25.00,             // ≥ £1 and ≤ maxPerPayment
    orderId: "joining-1001",   // your reference (≤ 50 chars)
    notes: "Joining fee",      // optional (≤ 30 chars)
  },
});

// One approval at contract.authorizationUrl activates the contract AND takes the charge.
await atoa.contract.awaitActive(contract.contractId);
const { initialCharge } = await atoa.contract.get(contract.contractId);
console.log(initialCharge?.status);  // AWAITING_APPROVAL → PENDING → COMPLETED | FAILED
```

Read the outcome from the contract's `initialCharge`.

`initialCharge.paymentRequestId` only exists once the customer approves. After that, poll `payment.get` with it;
before it, match on your own `orderId`. A status of `INITIATION_FAILED` means the charge never started — the
contract still becomes `ACTIVE`.

Only take the first charge with `payment.collect` when the contract was created **without** an `initialCharge`.
Doing both charges the customer twice.

Pass `storeId` to attribute the charge to one of your stores. That store's name and logo appear on the
customer's contract page.

## SCA on a charge

An off-session charge doesn't settle until the customer approves it.

`payment.collect` returns a `nextAction` (`next_action` in Python) holding an `approvalUrl`. Send the customer
there. They approve with a one-time code or a passkey on a page hosted by Atoa. Your app never sees the
credential.

```python Python theme={null}
share_with_customer(charge.next_action.approval_url)   # your own delivery — email, push, …
decision = atoa.payment.await_decision(charge.next_action.approval_id)
print(decision.status)                                  # APPROVED / DECLINED / EXPIRED

settled = atoa.payment.await_settled(charge.payment_request_id)
print(settled.status)                                   # COMPLETED
```

```typescript TypeScript theme={null}
shareWithCustomer(charge.nextAction.approvalUrl);    // your own delivery — email, push, …
const decision = await atoa.payment.awaitDecision(charge.nextAction.approvalId);
console.log(decision.status);                        // APPROVED / DECLINED / EXPIRED

const settled = await atoa.payment.awaitSettled(charge.paymentRequestId);
console.log(settled.status);                         // COMPLETED
```

To keep the customer on your own site instead of sending a URL, pass `nextAction.clientSecret` to
`confirmApproval` from the [Approvals SDK](https://docs.paywithatoa.com/agent-pay/approvals.md).

A declined charge comes back as `FAILED` with `failureReason: APPROVAL_DECLINED`. One nobody approved in time
comes back as `APPROVAL_EXPIRED`. Neither throws — read them off the `Payment`. In sandbox, open the
`approvalUrl` yourself and pick the decision you want to test.

## Refunds

Refund a `COMPLETED` collect, in full or in part. A refund moves `INITIATED → COMPLETED` (or `FAILED`).

```python Python theme={null}
refund = atoa.payment.refund(
    req.payment_request_id,
    amount={"amount": 10.00},       # omit for a full refund
    reason="Damaged item",
)
print(refund.status, refund.refund_amount)

atoa.payment.list_refunds(req.payment_request_id)       # all refunds on this payment
```

```typescript TypeScript theme={null}
const refund = await atoa.payment.refund(req.paymentRequestId, {
  amount: { amount: 10.00 },      // omit for a full refund
  reason: "Damaged item",
});
console.log(refund.status, refund.refundAmount.amount);

await atoa.payment.listRefunds(req.paymentRequestId);   // all refunds on this payment
```

A pending refund can be cancelled with `cancelRefund(refundId)`. **Sandbox:** set `reason` to `"FAILURE TEST"` to
force a `FAILED` refund and exercise that branch.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.