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

# Payment Processing

> Connect Hiveku Payments, your own Stripe, or Authorize.Net to charge your clients — with stored cards, charge-on-file, refunds, and a branded pay page

Commerce lets an agency charge its **own clients** through one of three payment processors. You connect a processor once, then every invoice can be paid on a hosted, branded pay page — and any card your client saves can be charged again on file for subscriptions and future invoices.

All three processors implement the same internal gateway interface, so the pay page, stored-card, refund, and reconciliation flows work identically regardless of which one you pick. Connections are stored per account in `crm_payment_integrations`; the Payments UI is at `/dashboard/commerce/payments`, and you connect processors from `/dashboard/commerce/settings`.

## The three processors

<CardGroup cols={2}>
  <Card title="Hiveku Payments" icon="bolt">
    White-label Stripe, onboarded inside Hiveku. Fastest to set up, no Stripe account of your own required. Hiveku charges a platform fee per transaction.
  </Card>

  <Card title="Your own Stripe" icon="stripe">
    Link a Stripe account you already own via Connect OAuth. You keep your own Stripe dashboard and payouts. No Hiveku platform fee.
  </Card>

  <Card title="Authorize.Net" icon="credit-card">
    Bring your Authorize.Net merchant credentials. US / USD. Accept.js pay page plus CIM stored cards. No Hiveku platform fee.
  </Card>
</CardGroup>

|                       | Hiveku Payments                              | Your own Stripe                       | Authorize.Net                      |
| --------------------- | -------------------------------------------- | ------------------------------------- | ---------------------------------- |
| **How you connect**   | Embedded Stripe onboarding (KYC in-app)      | Stripe Connect OAuth                  | Paste merchant API credentials     |
| **Underlying rail**   | Stripe (Connect direct charge)               | Stripe (Connect direct charge)        | Authorize.Net                      |
| **Hiveku fee**        | Yes — `3.9% + $0.60` per charge              | None                                  | None                               |
| **Card tokenization** | Stripe.js (`pm_…`)                           | Stripe.js (`pm_…`)                    | Accept.js (opaque token)           |
| **Stored cards**      | Stripe Customer + PaymentMethod              | Stripe Customer + PaymentMethod       | Authorize.Net CIM customer profile |
| **Currency**          | Defaults to US / USD                         | Your Stripe account's currencies      | US / USD                           |
| **Connection mode**   | `stripe_connection_mode = 'hiveku_payments'` | `stripe_connection_mode = 'standard'` | `provider = 'authorize_net'`       |

<Info>
  You can have more than one processor connected, but a single **active** gateway (`crm_payment_integrations.provider`) is what new invoice charges route through. If the selected provider isn't fully connected, Hiveku falls back to whichever one is — an account never hard-fails a charge because the column still reads the default. The default is Authorize.Net.
</Info>

## Connecting Hiveku Payments

Hiveku Payments is a fully white-labeled Stripe connected account that Hiveku creates and onboards for you. There's no separate Stripe login — KYC and account management happen inside Hiveku via Stripe's embedded Connect components.

<Steps>
  <Step title="Open payment settings">
    Go to `/dashboard/commerce/settings` and choose **Hiveku Payments**.
  </Step>

  <Step title="Complete embedded onboarding">
    Hiveku creates a connected account on first use and renders Stripe's embedded onboarding. You submit business and bank details in-app; Stripe manages compliance and risk.
  </Step>

  <Step title="Wait for charges to enable">
    When Stripe marks the account `charges_enabled`, the connection flips to active. Until then, charges are refused with a clear "finish Stripe onboarding" message.
  </Step>
</Steps>

<Note>
  Hiveku Payments carries a blended platform fee of **`3.9% + $0.60`** per transaction, attached as Stripe's `application_fee_amount` on every charge. This is one all-in rate — your client never sees a separate Stripe line, and Stripe's own processing cost is deducted out of it. On a refund, Hiveku returns its fee proportionally so you aren't left covering the refund and eating the fee.
</Note>

## Connecting your own Stripe

If you already have a Stripe account, link it with Connect OAuth. You keep your own Stripe dashboard, payouts, and pricing — **Hiveku charges no platform fee** on this connection.

<Steps>
  <Step title="Start the OAuth flow">
    From `/dashboard/commerce/settings`, choose **Connect your existing Stripe**. Hiveku redirects you to Stripe's hosted authorize page with `read_write` scope.
  </Step>

  <Step title="Authorize on Stripe">
    Sign in to your Stripe account and approve the connection. Stripe redirects back to Hiveku's OAuth callback, which exchanges the code for your connected account id (`acct_…`).
  </Step>

  <Step title="Confirm it's live">
    Hiveku reads your account's live `charges_enabled` status and stores the link as a `standard` connection. Once charges are enabled and Authorize.Net isn't also connected, Stripe is auto-selected as your active gateway.
  </Step>
</Steps>

<Warning>
  You can't link your own Stripe while a Hiveku Payments account is live — the flows use different account models and linking would orphan the Hiveku Payments account on Stripe. Disconnect Hiveku Payments first. Disconnecting a linked Stripe also revokes Hiveku's OAuth grant on your Stripe account.
</Warning>

## Connecting Authorize.Net

Authorize.Net is bring-your-own-credentials. It's **US / USD** and uses Accept.js for the pay page plus CIM (Customer Information Manager) for stored cards.

<Steps>
  <Step title="Gather your credentials">
    From your Authorize.Net merchant account you'll need four values: **API Login ID**, **Transaction Key**, **Signature Key**, and **Public Client Key** (the last one powers client-side Accept.js tokenization).
  </Step>

  <Step title="Enter them in settings">
    Go to `/dashboard/commerce/settings`, choose **Authorize.Net**, paste all four, and pick **sandbox** or **production**.
  </Step>

  <Step title="Hiveku verifies and stores">
    On save, Hiveku runs a no-charge authentication test against the gateway to confirm the keys are live, encrypts the secrets at rest, and records the connection with the verification result.
  </Step>

  <Step title="Webhooks self-register">
    When verification succeeds, Hiveku auto-registers a per-tenant webhook with Authorize.Net (settlement, refund, void, and fraud events) so payments reconcile without you pasting a URL into the Authorize.Net dashboard. If auto-registration fails, the settings UI tells you to register it manually.
  </Step>
</Steps>

<Warning>
  Sandbox credentials only work against the sandbox endpoint and production credentials only against production — a mismatch fails verification. The API Login ID is case-sensitive; paste it with no trailing whitespace.
</Warning>

## The pay page

Every invoice can be shared as a tokenized link that opens a hosted, branded pay page at `/pay/{token}`. The page renders your agency's logo, brand color, and the invoice's template design, shows the line items and balance due, and — when a processor is connected and the link allows payment — presents a **Pay now** panel:

* **Stripe (Hiveku Payments or your own)** renders a Stripe.js card element. The browser tokenizes the card into a PaymentMethod (`pm_…`); only that id reaches Hiveku.
* **Authorize.Net** renders an Accept.js form. The browser tokenizes the card into an opaque token; only that token reaches Hiveku.

Either way the raw card number never touches Hiveku's servers. On success the page shows a confirmation and transaction id, and the invoice balance updates. A read-only link (or a fully paid / voided invoice) shows the invoice without a pay form.

<Tip>
  The pay page is the same code path for both the public token link and the authenticated client portal, so a payment behaves identically wherever the client starts.
</Tip>

## Stored cards and charge-on-file

When a client opts to save a card, Hiveku stores it **at the processor** — never the card number itself — and keeps only a reference:

* **Stripe** — the card is attached to a Stripe Customer as a PaymentMethod. Stored charges confirm a PaymentIntent `off_session` against that customer + payment method.
* **Authorize.Net** — a CIM customer profile holds the payment profile. Hiveku snapshots the card's brand, last four, and expiry into `crm_customer_profiles.stored_payment_profiles` so the UI can list saved cards without round-tripping the gateway on every read.

A saved card can be charged again on file — this is what powers subscription renewals and one-click repeat payments. For recurring billing and failed-payment dunning, see [Subscriptions](/commerce/subscriptions).

## Refunds

Refund from the invoice's payment record. Both full and partial refunds go back to the original payment method at the processor, which is the source of truth; Hiveku reflects the result and updates the invoice's refunded amount and balance.

<Tabs>
  <Tab title="Stripe">
    Hiveku issues a refund against the original charge. For **Hiveku Payments**, the platform fee is refunded proportionally so you don't cover the refund and lose the fee. For **your own Stripe**, there's no Hiveku fee to return.
  </Tab>

  <Tab title="Authorize.Net">
    Hiveku tries a **void** first (free and instant) if the transaction hasn't settled yet. If it has already settled, it falls back to a **refund** by reference, which requires the card's last four. Refund events echoed back by the gateway reconcile the invoice's `amount_refunded_cents` and `balance_due_cents`.
  </Tab>
</Tabs>

Refunds appear on the client's statement on the processor's own timeline (typically several business days for cards). Refunds older than the processor's window may have to be handled in the processor's dashboard directly.

## Reconciliation

* **Automatic** — the synchronous pay flow records the payment immediately, and processor webhooks are an idempotent backstop that also carry async signals (settlement, dashboard-initiated refunds, onboarding completion). Duplicate webhook deliveries can't create duplicate ledger rows.
* **Manual** — for money received outside a processor (a check or wire), record it against the invoice with **Mark as Paid** (`POST /api/crm/invoices/{id}/record-payment`).

## Keeping card data off Hiveku's servers

Both rails tokenize the card in the browser (Stripe.js or Accept.js) and send Hiveku only a token — no card number, CVC, or expiry reaches Hiveku. Saved cards live at the processor (Stripe Customers or Authorize.Net CIM profiles), and Hiveku persists only references. This keeps card entry fully outsourced to the processor.

<Warning>
  Don't defeat the tokenization flow by taking card numbers over the phone, emailing them, or storing them in a spreadsheet. Always direct clients to the pay page.
</Warning>

## Under the hood

<Accordion title="Data model, routes, env vars, and webhooks (for developers)">
  **Provider resolution**

  The gateway for an account is resolved in `src/lib/billing/providers/index.ts` (`resolveProviderName` / `getPaymentProvider`). It honors `crm_payment_integrations.provider` (`'stripe'` or `'authorize_net'`) when that provider is connected, and otherwise falls back to whichever one is. Default: `authorize_net`. Both Stripe modes share the `stripe` provider value and the `stripe_account_id` column — they're distinguished by `stripe_connection_mode` (`'hiveku_payments'` vs `'standard'`).

  **Where connection state lives**

  `crm_payment_integrations` (one row per account) holds:

  * Authorize.Net: `api_login_id_encrypted`, `transaction_key_encrypted`, `signature_key_encrypted`, `public_client_key` (a merchant identifier, stored plaintext), `sandbox`, `merchant_id`, `webhook_endpoint_id`.
  * Stripe: `stripe_account_id`, `stripe_connection_mode`, `stripe_connect_status`, `stripe_charges_enabled`.
  * Shared: `provider`, `connected_at`, `last_verified_at`, `last_error`.

  Authorize.Net secrets are encrypted at rest via `src/lib/crypto/encrypt.ts` (`encryptApiKey` / `decryptApiKey`) and cached per account for 5 minutes. Stripe never stores merchant secrets in this table — charges run on the platform secret key with a `Stripe-Account` header scoping each call to the connected account (Connect direct charges).

  The payment ledger is `crm_payments`, unique on `(account_id, gateway, gateway_transaction_id)` — that constraint is what makes webhook replays idempotent. Import/migration runs are tracked in `crm_payment_migrations`.

  **Settings routes** (the `/settings/*` rows are gated by the `crm.invoicing` permission — `read` to view status, `create`/`manage` to onboard or switch, `delete` to disconnect; the public pay endpoint is token-scoped, not permission-gated):

  | Route                                                     | Purpose                                                                                                         |
  | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
  | `POST/GET/DELETE /api/crm/billing/settings/authorize-net` | Save / read / soft-revoke Authorize.Net credentials                                                             |
  | `POST /api/crm/billing/settings/stripe/account-session`   | Mint an embedded-onboarding client secret; creates the Hiveku Payments account on first call                    |
  | `POST /api/crm/billing/settings/stripe/oauth/start`       | Begin BYO Stripe Connect OAuth                                                                                  |
  | `GET /api/crm/billing/settings/stripe/oauth/callback`     | Exchange the OAuth code, store the linked `standard` account                                                    |
  | `POST /api/crm/billing/settings/stripe/connect`           | Hosted Stripe onboarding (Account Link) alternative                                                             |
  | `GET/DELETE /api/crm/billing/settings/stripe`             | Read live Stripe status / disconnect                                                                            |
  | `POST /api/crm/billing/settings/provider`                 | Select the active gateway (`{ provider: 'stripe' \| 'authorize_net' }`); refuses a gateway that isn't connected |
  | `POST /api/crm/billing/settings/migrate/{gateway}`        | Import existing customers/cards/transactions from a linked account                                              |
  | `POST /api/public/billing/{token}/pay`                    | Public, token-scoped charge endpoint used by the pay page                                                       |

  **Server env vars**

  * `STRIPE_SECRET_KEY` + `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` — required for any Stripe flow (`stripeConfigured()`).
  * `STRIPE_CONNECT_CLIENT_ID` — required for BYO Stripe Connect OAuth.
  * `STRIPE_CONNECT_WEBHOOK_SECRET` — signing secret for the platform Connect webhook.
  * `NEXT_PUBLIC_BUILDER_URL` — app origin used to build OAuth redirect URIs and the Authorize.Net webhook URL (defaults to `https://app.hiveku.com`).

  There is no env-var toggle for the platform fee — it's hardcoded in `src/lib/billing/stripe/platform-fee.ts` (`PLATFORM_FEE_BPS = 390`, `PLATFORM_FEE_FLAT_CENTS = 60`, `PLATFORM_FEE_LABEL = '3.9% + $0.60'`).

  **Webhook endpoints**

  * **Stripe** — one platform endpoint, `POST /api/webhooks/stripe/connect`, receives events for every connected account and routes by `event.account`. Verified with HMAC-SHA256 over `${timestamp}.${rawBody}` using `STRIPE_CONNECT_WEBHOOK_SECRET` (the `stripe-signature` header).
  * **Authorize.Net** — per-tenant endpoint, `POST /api/webhooks/authorize-net/{accountId}`. The account id in the path is a routing hint only; trust comes from HMAC-SHA512 over the raw body using that account's Signature Key (the `x-anet-signature` header), plus a 5-minute timestamp window. Auto-registered on connect via Authorize.Net's REST API (`/rest/v1/webhooks`, HTTP Basic auth = API Login ID : Transaction Key).

  **Gateway endpoints**

  * Stripe REST: `https://api.stripe.com` (charges via `/v1/payment_intents`, refunds via `/v1/refunds`, stored cards via `/v1/customers` + `/v1/payment_methods`); Connect OAuth on `https://connect.stripe.com` (`/oauth/authorize`, `/oauth/token`, `/oauth/deauthorize`).
  * Authorize.Net transaction API: `https://api.authorize.net/xml/v1/request.api` (production) / `https://apitest.authorize.net/xml/v1/request.api` (sandbox).
</Accordion>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Stripe shows connected but charges fail">
    A connected Stripe account can't charge until `charges_enabled` is true. Finish Stripe onboarding (or clear any verification requirements Stripe placed on the account) and re-check the status on the settings page, which re-reads it live.
  </Accordion>

  <Accordion title="Can't link my own Stripe">
    Linking your own Stripe is blocked while a Hiveku Payments account is live (it would orphan that account on Stripe). Disconnect Hiveku Payments first, then run the OAuth flow.
  </Accordion>

  <Accordion title="Authorize.Net verification fails">
    Re-copy the API Login ID and Transaction Key without trailing whitespace, and confirm you selected the matching environment — sandbox credentials don't authenticate against production and vice versa.
  </Accordion>

  <Accordion title="Authorize.Net webhook didn't auto-register">
    The settings response tells you whether reconciliation webhooks were registered. If not, register the URL shown in settings manually in your Authorize.Net dashboard. The synchronous charge path still updates invoices without webhooks; webhooks add settlement/refund/void reconciliation.
  </Accordion>

  <Accordion title="A refund won't go through">
    An Authorize.Net refund of a settled transaction needs the original card's last four. A very old transaction may fall outside the processor's refund window and has to be refunded from the processor's own dashboard.
  </Accordion>
</AccordionGroup>

## What's next?

<CardGroup cols={2}>
  <Card title="Send Your First Invoice" icon="rocket" href="/how-tos/send-first-invoice">
    End-to-end, including connecting a processor.
  </Card>

  <Card title="Subscriptions" icon="repeat" href="/commerce/subscriptions">
    Recurring billing on saved cards and dunning.
  </Card>

  <Card title="Client Portal" icon="user-tie" href="/commerce/client-portal">
    Where clients view invoices and pay.
  </Card>

  <Card title="Reports & Analytics" icon="chart-line" href="/commerce/reports">
    Cash collected, transaction volume, and refunds.
  </Card>
</CardGroup>
