# Private Checkout (https://dev.cosmospay.lat/docs/api/zk-checkout/)

> What the payer's browser reads and calls without a key: one private payment, the network's confidential-token setup, and the confirmation of the two confidential transfers that pay it.

Base URL: `https://api.cosmospay.lat/cosmos-api`

## A network's private payments setup, keyless: token, registries, auditor id, commission, fee account

`GET /v1/zk-checkout/config`

The same shape as `GET /v1/zk-payments/config`, built by the same code, for a page with no API key: the merchant's confidential-account page (register, balance, withdraw) and the checkout read the contracts they prove against here. Everything in it is public on-chain data. `enabled` is false, and the rest null, on a network where private payments do not run yet (mainnet today).

No authentication.

**Query parameters**

- `network` (string, required): The network to describe. Named by the caller, since a keyless page has no API key to take it from. One of: `testnet`, `public`.

**Responses**

- `200` (`ZkPaymentsConfigEntity`): OK
  - `enabled` (boolean, required): Whether private payments run on this network. False until the operator deploys the token there (mainnet today); every other field but `network`, `networkPassphrase`, `auditorId` and `feeBps` is then null.
  - `network` (string, required): The network the API key's environment selects. One of: `public`, `testnet`.
  - `rpcUrl` (string, required): The Soroban RPC to build and submit transactions through.
  - `networkPassphrase` (string, required): The Stellar network passphrase of `network`.
  - `token` (ZkTokenEntity, required): Cosmos Pay's confidential token on this network; null when `enabled` is false.
  - `verifier` (string, required): The UltraHonk verifier registry the token checks proofs with.
  - `auditorRegistry` (string, required): The auditor registry holding Cosmos Pay's key.
  - `auditorId` (number, required): The auditor id every account on the token registers under.
  - `feeBps` (number, required): The commission: 18 bps = 0.18%.
  - `feeAccount` (string, required): Cosmos Pay's account the commission is transferred to.
- `400`: ValidationFailed (see Shared responses)
- `429`: RateLimited (see Shared responses)
- `500`: InternalError (see Shared responses)

## Read a private payment as its payer sees it (no API key)

`GET /v1/zk-checkout/{id}`

For a checkout: the gross amount, the net and the commission, the merchant and the fee account to transfer to, the token and the RPC to prove and submit with, and the live status to poll.

**The id is the capability.** Nothing about the owning merchant account, the idempotency key or the settling transactions is returned.

No authentication.

**Path parameters**

- `id` (string, required): The private payment id (a cuid), as the merchant shared it.

**Responses**

- `200` (`PublicZkPaymentEntity`): OK
  - `id` (string, required): The private payment id (a cuid).
  - `status` (string, required): As it reads now: a PENDING payment past `expiresAt` reads EXPIRED, and can still settle with transfers that closed before `expiresAt`. One of: `PENDING`, `SUCCEEDED`, `EXPIRED`.
  - `amount` (string, required): What the payer pays in total, in XLM (decimal string): `netAmount + feeAmount`, plus `merchantFee.amount` when there is one.
  - `netAmount` (string, required): What the merchant receives, in XLM (decimal string): the amount of the transfer to `recipient`.
  - `feeAmount` (string, required): Cosmos Pay's commission, in XLM (decimal string): the amount of the transfer to `feeAccount`.
  - `feeBps` (number, required): The commission rate the payment was priced at, in basis points (1 = 0.01%).
  - `recipient` (string, required): Transfer `netAmount` here.
  - `feeAccount` (string, required): Transfer `feeAmount` here, from the same account.
  - `merchantFee` (MerchantFeeEntity, required): When not null, a third transfer: `merchantFee.amount` to `merchantFee.wallet`, from the same account.
  - `description` (string, required): The merchant's description of the payment, to show the payer; null when none was given.
  - `expiresAt` (string (date-time), required): When the payment stops being payable (ISO 8601 date-time). Every transfer must close before it.
  - `network` (string, required): The network the payment is on, and the transfers must be made on. One of: `public`, `testnet`.
  - `rpcUrl` (string, required): The Soroban RPC to build and submit the transfers through.
  - `networkPassphrase` (string, required): The Stellar network passphrase of `network`.
  - `token` (ZkTokenEntity, required): The confidential token the transfers are made on.
  - `auditorId` (number, required): The auditor id the transfers are encrypted to: Cosmos Pay reads the amount with it to confirm the payment.
- `400` (`ApiErrorBodyEntity`): `network_unsupported`: the payment's network or token is no longer served by this deployment.
  - `statusCode` (number, required): Always the HTTP status of the response itself.
  - `code` (string, required): Stable machine-readable code. Branch on this, never on the message: messages are human-facing and may be reworded; codes are never renamed once published. Each response documents the subset it can return. One of: `insufficient_scope`, `no_authenticated_consumer`, `gateway_required`, `admin_console_only`, `not_found`, `validation_failed`, `payload_too_large`, `idempotency_conflict`, `operation_in_flight`, `invalid_state_transition`, `slippage_exceeded`, `insufficient_balance`, `no_path_found`, `trustline_missing`, `invalid_amount`, `invalid_memo`, `transaction_rejected`, `transaction_already_settled`, `recipient_not_registered`, `payment_transfer_mismatch`, `fee_transfer_mismatch`, `merchant_fee_transfer_mismatch`, `payer_mismatch`, `transaction_not_found`, `expired`, `network_unsupported`, `asset_unsupported`, `merchant_fee_unsupported`, `provider_error`, `provider_unavailable`, `quote_not_found`, `quote_expired`, `kyc_state_invalid`, `kyc_review_required`, `account_disabled`, `payload_expired`, `rate_limited`, `customer_wallet_taken`, `alias_taken`, `alias_name_invalid`, `alias_challenge_invalid`, `alias_signature_invalid`, `alias_recovery_invalid`, `alias_address_conflict`, `wallet_handshake_invalid`, `wallet_verifier_invalid`, `wallet_login_code_invalid`, `wallet_login_code_cooldown`, `wallet_session_invalid`, `wallet_signature_invalid`, `wallet_backup_invalid`, `wallet_backup_limit`, `wallet_account_mismatch`, `wallet_provider_unavailable`, `wallet_return_url_not_allowed`, `wallet_recovery_setup_refused`, `easy_recovery_not_found`, `easy_recovery_already_offered`, `easy_recovery_not_offered`, `easy_recovery_code_invalid`, `easy_recovery_burned`, `plugin_not_installed`, `plugin_consent_mismatch`, `plugin_rejected`, `plugin_quota_exceeded`, `plugin_failed`, `protected_payment_buyer_mismatch`, `protected_payment_signature_invalid`, `misconfigured`, `internal_error`.
  - `error` (string, required): The HTTP reason phrase for `statusCode`.
  - `message` (string | string[], required): Human-readable detail. An array when class-validator rejected the body.
  - `details` (object, optional): Machine-readable facts about this failure, present only on the codes whose example shows it: `easy_recovery_code_invalid` carries `{ "attemptsLeft": 4 }`. Absent everywhere else.
  - `path` (string, required): The path of the request that failed.
  - `timestamp` (string, required): When the envelope was produced, ISO 8601.
- `404` (`ApiErrorBodyEntity`): No private payment has this id. The same answer the owner gets from `GET /v1/zk-payments/{id}` for one that never existed.
- `429`: RateLimited (see Shared responses)
- `500`: InternalError (see Shared responses)

## Confirm a private payment with its confidential transfers, verified on-chain (no API key)

`POST /v1/zk-checkout/{id}/confirm`

Reads both transactions back through the Soroban RPC and settles the payment only when: both succeeded and each is a single `confidential_transfer` on the token; both closed while the payment was open (not before it was created, not after `expiresAt`); both come from the same payer; the payment transfer goes to the recipient and the fee transfer to the fee account; and each decrypts with the auditor key, on both auditor channels and against the transfer's commitment, to exactly `netAmount` / `feeAmount`. Neither transaction may have settled another private payment.

Idempotent: the same hashes on a settled payment answer it again. A refusal leaves the payment PENDING, and never says what an amount decrypted to.

A payment with a `merchantFee` has a third transfer, of `merchantFee.amount` to `merchantFee.wallet`: send its hash as `merchantFeeTxHash` (required for such a payment, refused for any other). It is checked like the other two, in the same order.

No authentication.

**Path parameters**

- `id` (string, required): The private payment id (a cuid), as the merchant shared it.

**Request body** (`application/json`, required): `ConfirmZkPaymentDto`

- `paymentTxHash` (string, required): The transaction of the confidential transfer that paid the merchant the payment's `netAmount` (64 hex).
- `feeTxHash` (string, required): The transaction of the confidential transfer that paid the fee account the payment's `feeAmount` (64 hex), from the same account.
- `merchantFeeTxHash` (string, optional): Only for a payment with a merchant fee, and then required: the transaction of the confidential transfer that paid `merchantFee.wallet` the payment's `merchantFee.amount` (64 hex), from the same account.

**Responses**

- `200` (`PublicZkPaymentEntity`): OK
- `400` (`ApiErrorBodyEntity`): `transaction_not_found`: the RPC has no such transaction yet (retry in a few seconds) or no longer. `transaction_rejected`: it failed on-chain. `payment_transfer_mismatch` / `fee_transfer_mismatch` / `merchant_fee_transfer_mismatch`: that transaction is not the confidential transfer this payment needs (wrong token, shape, recipient, amount, or it closed before the payment existed). `payer_mismatch`: the transfers come from different accounts. `expired`: a transfer closed after `expiresAt`. `invalid_state_transition`: already settled by other transactions. `validation_failed` also answers a `merchantFeeTxHash` missing on a payment with a merchant fee, or sent for one without.
- `404` (`ApiErrorBodyEntity`): No private payment has this id. The same answer the owner gets from `GET /v1/zk-payments/{id}` for one that never existed.
- `409` (`ApiErrorBodyEntity`): `transaction_already_settled`: one of the transactions already settled another private payment, of any merchant. One transaction settles at most one payment.
- `429`: RateLimited (see Shared responses)
- `500`: InternalError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)

## Shared responses

- **ValidationFailed** (`ApiErrorBodyEntity`): The body, query or path failed validation. `message` is an array of the individual failures. Routes that reject a *valid* request for a reason of their own (an amount, a memo, a state) document the code for it on their own 400.
- **RateLimited** (`ApiErrorBodyEntity`): The budget in `x-cosmos-rate-limit` is spent for this caller. Honour `Retry-After`. The `ratelimit-*` triple rides along on successful responses too, so a client can pace itself instead of being refused. If the counter store itself is unreachable the guard fails closed and answers `503 provider_unavailable`.
- **InternalError** (`ApiErrorBodyEntity`): Unexpected server error. The detail is logged server-side and never returned: there is nothing in the body to branch on beyond the code.
- **UpstreamUnavailable** (`ApiErrorBodyEntity`): `provider_unavailable`: the provider in `x-cosmos-upstream` is down or unreachable; the message names it. Retry. `misconfigured`: this deployment is missing configuration the route needs (a provider instance, a fee wallet, the plan header the gateway must forward). Retrying will not help; it is an operator problem.
