# Wallet & Payments

> Prepaid workspace balance with multiple payment providers and auto-recharge

Source: https://docs.soosh.io/features/wallet/

Every organization has a prepaid **wallet**. Admins add funds under **Settings → Wallet** with any payment provider the server has enabled, and can turn on **auto-recharge** so a saved card tops the balance up whenever it drops below a threshold.

## Payment providers

Several providers can be enabled at once, so top-ups never depend on a single gateway. Each one is offered as soon as its keys are in the `[payments]` config (see `config.example.toml`).

| Provider | What customers pay with | Saved cards / auto-recharge | Test mode |
|---|---|---|---|
| **Sandbox** (built in) | Test card numbers on an in-app page | Yes | Always; no keys needed |
| **Stripe** | Cards on Stripe Checkout | Yes | `sk_test_…` key, card `4242 4242 4242 4242` |
| **Razorpay** | UPI, cards, netbanking, wallets (Payment Links) | No | `rzp_test_…` keys |
| **PayPal** | PayPal balance or cards (Orders v2) | No | `live = false` (sandbox), sandbox buyer accounts |

> **Careful:** The sandbox gateway credits wallets without any money moving. It is opt-in: it runs only with `payments.sandbox = true`, or when `app.environment` is explicitly set to `test` and no real provider is configured. A missing or `development` environment does not turn it on. If it is on beside real providers the server logs an error. Keep it off wherever balances matter.

### Sandbox test cards

| Card | Result |
|---|---|
| `4242 4242 4242 4242`, `5555 5555 5555 4444` | Succeeds |
| `4000 0000 0000 0002` | Declined |
| `4000 0000 0000 9995` | Insufficient funds |
| `4000 0000 0000 0341` | Succeeds now; later auto-recharges with it fail |

Any future expiry date and any 3-digit CVC work.

## How a top-up completes

1. `POST /api/wallet/topup` creates a pending payment and returns the provider's page.
2. The customer pays and is sent back to `/settings/wallet?payment=…`, which asks the server to confirm with the provider.
3. Provider webhooks (`/api/wallet/webhooks/{stripe|razorpay|paypal}`) and a background check every few minutes also confirm payments, so a closed tab never loses a payment.

The server always reads the result back from the provider's API with its own keys; a webhook body is never trusted for amounts. Each payment credits the wallet at most once.

### Cancellation and expiry

A top-up is only marked canceled once the provider confirms its checkout can no longer be paid: the Stripe Checkout Session is expired, the Razorpay Payment Link is cancelled (links are also created with a 24-hour `expire_by`), and a PayPal order is simply never captured. If the provider can't be reached, or reports the payment was made a moment earlier, the payment stays pending or is credited. This happens when the customer backs out of the provider page, and in the background for top-ups still pending after 24 hours; age alone never cancels a payment.

If a provider ever reports a canceled or failed payment as paid, it is credited then, still at most once.

## Auto-recharge

Choose a threshold, an amount and a saved card. When the balance falls below the threshold (after any debit, or at the next once-a-minute check) the card is charged off-session. Only an explicit decline counts as a failed attempt: if the outcome is unknown (timeout, lost response, provider error) the attempt stays pending and is resolved with the same payment id, by replaying it under the same idempotency key or searching Stripe for it, before any new charge is made. Attempts are at least 10 minutes apart, at most 5 succeed or run per 24 hours, the threshold can't exceed the maximum top-up, and auto-recharge turns itself off after 3 failures in a row, showing the last error on the wallet page.

## Charging the wallet

> **Note:** **Usage charging is not active yet.** Messages, campaigns and calls are not charged to the wallet, and they keep working whatever the balance; the wallet page says so. WhatsApp's own fees are still billed by Meta to each WhatsApp Business Account's payment method. Charging usage needs prices per message category, country and call minute, and a decision on what happens when the balance runs out.

Server code debits the wallet through `wallet.Service.Debit`, which records a ledger entry (give each one a stable reference such as `message:<id>` so a retried event is charged once), refuses to go below zero unless told to, and starts an auto-recharge when needed. Super admins can also credit or debit any workspace by hand under **Platform → Organizations → (org) → Wallet** (`POST /api/admin/organizations/{id}/wallet/adjust`).

## Permissions

The `billing` resource controls access: `billing:read` to see the wallet, `billing:write` to add funds and manage auto-recharge and cards. Admin roles get both.
