# Bank Payout

Let a user convert crypto into fiat on their bank account through the bank ramp's hosted payout page. Payouts currently run only in the bank ramp's sandbox (`isTestnet: true`); production requests are refused.

**This is the hosted payout**, where the bank ramp's own page takes the crypto out of *Aurea's* balance. To have the user pay from a wallet they control — which is what most integrations want — read [Wallet Pay-Out](https://docs.aureahub.com/docs/guide-wallet-payout.md), or [Standalone Pay-Out](https://docs.aureahub.com/docs/guide-standalone-payout.md) when the wallet is one Aurea has no key for.

## Overview

You create a payout for a crypto amount; Aurea gets the rate from the bank ramp and returns a hosted URL; the user chooses a bank account and confirms on the hosted page; the bank ramp's events update the payout in Aurea. The user never types an IBAN into your app.

## Before You Start

- Aurea has connected the bank ramp for your tenant and switched on payouts in the sandbox — [Bank Ramp Setup](https://docs.aureahub.com/docs/bank-setup.md). [Tenant Settings](https://docs.aureahub.com/docs/bank-settings.md) lists the currencies it offers for payout.
- Payouts run only in the bank ramp's sandbox for now: send `isTestnet: true`. This hosted payout is funded from the bank ramp balance Aurea keeps for your tenant, not from the user's wallet: when a payout is refused for balance, ask Aurea to top it up. A production request gets `403` `PAYOUT_PRODUCTION_UNAVAILABLE`.
- The user has completed the bank ramp's onboarding — see [Fiat Pay-In](https://docs.aureahub.com/docs/guide-fiat-payin.md), steps 1–3.

## Step 1: Check KYC

Payouts run in the sandbox, so read the user's **sandbox** profile: a user has a separate profile, with its own KYC, in each the bank ramp environment.

```typescript
const status = await fetch('https://api.aureahub.com/v1/ramp/bank/payin/onboarding-status?isTestnet=true', {
  headers: { Authorization: `Bearer ${token}` }
}).then(r => r.json());

if (status.kycStatus !== 'approved') {
  // Payouts require kycStatus "approved" in the sandbox — onboard with isTestnet: true first
  return startBankOnboarding();
}
```

## Step 2: Create the Payout

`cryptoAmount` is an integer string with 6 decimals (`"25000000"` = 25). `returnUrl` must be one your tenant allows ([Bank Ramp Setup](https://docs.aureahub.com/docs/bank-setup.md)). Send an `Idempotency-Key`, and the same key on every retry, so a retry never creates a second payout ([Idempotency](https://docs.aureahub.com/docs/idempotency.md)).

```typescript
const res = await fetch('https://api.aureahub.com/v1/ramp/bank/payout/initiate', {
  method: 'POST',
  headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'Idempotency-Key': payoutKey },
  body: JSON.stringify({
    cryptoCurrency: 'USDC_TEST',
    cryptoAmount: '25000000',
    fiatCurrency: 'EUR',
    returnUrl: 'myapp://payout/done',
    isTestnet: true
  })
});
const payout = await res.json();
if (!res.ok) throw new Error(payout.message);

// { payoutId, checkoutUrl, fiatAmount, fiatCurrency, exchangeRate, expiresAt }
savePayoutId(payout.payoutId);
```

## Step 3: Open the Hosted Page

Show the quote (`fiatAmount`, `exchangeRate`), then open `checkoutUrl`. When the user is done, the hosted page returns them to Aurea, which sends them to your `returnUrl` unchanged — the redirect carries no result.

```typescript
await InAppBrowser.open(payout.checkoutUrl);
// ...later, your deep-link handler for myapp://payout/done runs Step 4
```

## Step 4: Track the Status

A payout starts as `pending` and moves to `processing`, then `completed` or `failed`, as the bank ramp reports the payout to Aurea. Read the payout — or receive `ramp.payout.updated` on your server ([Events to Your Server](https://docs.aureahub.com/docs/guide-events.md)) — and let the push notification Aurea sends on completion or failure prompt a refresh.

```typescript
const current = await fetch(
  `https://api.aureahub.com/v1/ramp/bank/payout/transactions/${payoutId}?isTestnet=true`, // payouts run in the sandbox
  { headers: { Authorization: `Bearer ${token}` } }
).then(r => r.json());

switch (current.status) {
  case 'pending':    /* user hasn't finished on the hosted page yet */ break;
  case 'processing': /* confirmed, on its way */ break;
  case 'completed':  /* fiat sent to the bank account */ break;
  case 'failed':
  case 'cancelled':  /* show the outcome and offer to try again */ break;
}
```

## Errors

Initiate Payout answers `403` `PAYOUT_PRODUCTION_UNAVAILABLE` to a production request (`isTestnet` false or omitted), `403` `NOAH_FUNCTION_OFF` when your tenant has payouts switched off, and `400` with a descriptive `message` when:

- the return URL is not one your tenant allows (`RETURN_URL_NOT_ALLOWED`);
- your tenant doesn't offer the currency for payout (`NOAH_PAIR_UNAVAILABLE`);
- the user never onboarded or `kycStatus` isn't `approved`;
- the bank ramp has no channel for the currency pair;
- the fiat amount is below the channel minimum or above its maximum (`details` contains the limit);
- the bank ramp balance Aurea keeps for your tenant is too low.

See [Initiate Payout](https://docs.aureahub.com/docs/payout-initiate.md) for the exact messages.

---

Web version: https://docs.aureahub.com/#guide-fiat-payout
