Loading module
Resolving locale, route permissions, and workspace projection.
Resolving locale, route permissions, and workspace projection.
Current scope: Guest
Category: 90_stabilization | Version: v1.0.0
Owner: DOCUMENT_CUSTODIAN | Review cycle: 90 days
Approval authority: Unspecified
Documentation portal is read-only. Editing and mutation endpoints are disabled.
Kvary platform is originally created in Georgian. Where a Georgian version exists, Georgian is authoritative for platform UI, documentation, and legal interpretation.
Translations into other languages are provided for convenience. Some records may originate in other languages and carry their own source or legal locale for a specific flow, but where a Georgian version is available, the Georgian version prevails for platform-level wording and interpretation.
Metadata incomplete: Document ID, Version, Status, Owner Role, Last Review Date, Next Review Date, Change Log
This is the narrow operator/developer runbook for the messaging, call, and payments stack after the hardening pass.
It covers:
http://127.0.0.1:4001/api/v1/healthhttp://127.0.0.1:4026/healthhttp://127.0.0.1:4027/healthhttp://127.0.0.1:4591/healthThe messaging health payload now reports:
The payments health payload now reports:
ready for real transfer executionok reflects local service health.
ready reflects whether the configured real-transfer path is usable.
Set these in local development when testing real Algorand transfers:
MESSAGING_SERVICE_URL=http://localhost:4026PAYMENTS_SERVICE_URL=http://localhost:4027PAYMENTS_ALGORAND_MODE=TESTNET_SIGNERALGOD_TESTNET_URL=https://testnet-api.algonode.cloudALGORAND_SIGNER_URL=http://127.0.0.1:4591/sign/algorand/paymentALGORAND_SIGNER_MNEMONIC=...Never commit a real mnemonic.
The dev signer is still a deliberate trust boundary for local/test usage.
For payment signing it now:
svc-paymentsThe web live connection now:
onlinefocusThe call shell now revalidates the active call when the websocket transport reopens.
Run:
npm run smoke:messaging-payments
Optional real-transfer readiness mode:
MESSAGING_PAYMENTS_SMOKE_EXPECT_SIGNER=1 npm run smoke:messaging-payments
The smoke script validates:
When reconciliation is enabled, run:
PAYMENTS_RECONCILIATION_ENABLED=true npm --prefix services/svc-payments run work:payments-reconciliation
Each scan now synchronizes persisted reconciliation findings:
RESOLVEDflagged and resolved counts for the scanDetected conditions:
STUCK_PENDING: a payment projection remains PENDING beyond PAYMENTS_PENDING_STUCK_THRESHOLD_SECONDSCONFIRMED_WITHOUT_FINALITY: execution is confirmed while finality has not been recorded after PAYMENTS_RECONCILIATION_FINALITY_GRACE_SECONDSFAILED_WITHOUT_FINALITY: execution failed while finality has not been recorded after the grace windowLifecycle semantics:
OPEN means the finding is present in the latest full reconciliation scan.RESOLVED means the finding disappeared from a later full scan; the row is retained for audit history.(intent_id, finding_code) appears after resolution, the existing row reopens instead of creating a duplicate.Operator visibility:
GET /api/v1/payments/admin/payments/reconciliation?limit=50GET /admin/payments/reconciliation?limit=50/admin/paymentsfindingId, intentId, optional groupId, findingCode, findingStatus, first/last/resolved timestamps, and current projection status before taking any manual action.Reconciliation is detect-only. It must not create custody, debit accounts, submit automatic retries, execute fallback rails, or mutate settlement state. Retry/fallback decisions remain manual operator actions outside this worker and outside the read-only admin view.
svc-payments uses Keepz as the primary BANK settlement provider when BANK rails are enabled:
PAYMENTS_BANK_LIVE_ENABLED=true \
PAYMENTS_LIVE_ENABLED_RAILS=BANK,CRYPTO \
PAYMENTS_BANK_PROVIDER_TYPE=KEEPZ \
KEEPZ_EXECUTION_MODE=DRY_RUN \
npm --prefix services/svc-payments run dev
For live Keepz execution, set KEEPZ_EXECUTION_MODE=LIVE, KEEPZ_API_BASE_URL, and KEEPZ_API_KEY. For sandbox execution, set KEEPZ_EXECUTION_MODE=SANDBOX, KEEPZ_CLIENT_ID, KEEPZ_CLIENT_SECRET, and optionally KEEPZ_SANDBOX_BASE_URL. Keepz beneficiary settlement profiles must provide bankAccountRef as either a Georgian IBAN or Keepz receiver UUID.
Buy Now settlement uses one Keepz eCommerce order with splitDetails: the buyer pays the full order amount once, Keepz settles the seller net leg and the Kvary platform commission leg. Kvary does not receive the full buyer amount, does not hold escrow, and does not create a fallback direct transfer when split order readiness fails.
Enable this path only after Keepz confirms eCommerce split order support for the merchant account:
PAYMENTS_BANK_PROVIDER_TYPE=KEEPZ \
KEEPZ_SPLIT_ORDER_ENABLED=true
Create the draft through the payments API or API gateway:
POST /admin/payments/buy-now-split-orderPOST /payments/buy-now-split-orderPOST /api/v1/payments/admin/payments/buy-now-split-orderPOST /api/v1/payments/buy-now-split-orderPOST /api/auctions/:id/buy-now-paymentThe web auction bridge resolves the seller from auction detail and the Kvary commission recipient from server-side configuration before calling the gateway. Set one of BUY_NOW_PLATFORM_ACCOUNT_ID, KVARY_PLATFORM_ACCOUNT_ID, or PAYMENTS_PLATFORM_ACCOUNT_ID; set BUY_NOW_PLATFORM_COMMISSION_BPS when the default 300 bps commission should be overridden.
Server-side environment for this buyer-facing bridge:
API_GATEWAY_BASE_URL, AUCTION_DECLARATION_SERVICE_URL or TENDERS_SERVICE_URL, BUY_NOW_PLATFORM_ACCOUNT_ID or KVARY_PLATFORM_ACCOUNT_ID or PAYMENTS_PLATFORM_ACCOUNT_ID, and optional BUY_NOW_PLATFORM_COMMISSION_BPS.PAYMENTS_SERVICE_URL.PAYMENTS_BANK_PROVIDER_TYPE=KEEPZ, KEEPZ_SPLIT_ORDER_ENABLED=true, PAYMENTS_BANK_LIVE_ENABLED, PAYMENTS_LIVE_ENABLED_RAILS, KEEPZ_EXECUTION_MODE, KEEPZ_FORCE_DRY_RUN, KEEPZ_API_BASE_URL, KEEPZ_API_KEY, KEEPZ_CLIENT_ID, KEEPZ_CLIENT_SECRET, KEEPZ_SANDBOX_BASE_URL, KEEPZ_LIVE_BASE_URL, KEEPZ_AUTH_TIMEOUT_MS, KEEPZ_TRANSACTION_TIMEOUT_MS, and KEEPZ_TIMEOUT_MS.Do not expose Keepz credentials or Kvary platform recipient ids through NEXT_PUBLIC_ variables. Receiver kind is not a separate browser input: settlement profile bankAccountRef encodes it as keepz:user:<receiver-id>, keepz:branch:<branch-id>, keepz:iban:<iban>, a Georgian IBAN, or a Keepz receiver UUID.
Required profiles:
VERIFIED, support BANK, and expose a Keepz-recognized bankAccountRef.VERIFIED, support BANK, and expose a Keepz-recognized bankAccountRef.bankAccountRef formats are keepz:user:<receiver-id>, keepz:branch:<branch-id>, keepz:iban:<iban>, a Georgian IBAN, or a Keepz receiver UUID.Readiness failures return 409 and do not create a payment intent. Important reason codes are KEEPZ_SPLIT_ORDER_NOT_CONFIGURED, SELLER_PAYOUT_PROFILE_MISSING, PLATFORM_COMMISSION_RECIPIENT_MISSING, and SPLIT_LEGS_UNBALANCED. Commission BPS calculations use minor units and round down; the seller leg receives the remainder so the two splitDetails entries always sum to the buyer payable total.
Run this smoke after building svc-payments; it prints the canonical Keepz create-order payload and does not call live or sandbox rails:
npm --prefix services/svc-payments run build
npm run smoke:keepz-buy-now-split
Expected smoke scenario: buyer total 10000.00 GEL, commission 300.00 GEL, seller net 9700.00 GEL. The printed Keepz payload must contain gross amount: 10000, two splitDetails legs, the seller receiver, the Kvary commission receiver, a deterministic uniqueId, and no commissionType, toIban, or direct receiverId fields in split-order mode.
Example split payload shape:
{
"amount": 10000,
"currency": "GEL",
"description": "Buy Now smoke auction-smoke-10000-gel x1",
"uniqueId": "<deterministic-uuid>",
"splitDetails": [
{
"receiverType": "USER",
"receiverIdentifier": "seller-smoke-receiver",
"amount": 9700
},
{
"receiverType": "BRANCH",
"receiverIdentifier": "kvary-platform-commission",
"amount": 300
}
]
}
The browser calls POST /api/auctions/:id/buy-now-payment with buyer account and quantity only. The web route fetches auction detail from the auction declaration service, reads auction.ownerStakeholderId as the seller account, reads the Kvary platform account from server env, and forwards both to POST /api/v1/payments/buy-now-split-order. svc-payments then loads seller and platform settlement profiles from party_settlement_profiles; each profile must be VERIFIED, support BANK, and expose a Keepz-recognized bankAccountRef.
Missing seller account blocks in the web bridge with SELLER_PAYOUT_PROFILE_MISSING. Missing or invalid seller settlement profile blocks in svc-payments with SELLER_PAYOUT_PROFILE_MISSING. Both paths return 409 and do not create a payment intent.
There is currently no dedicated Keepz webhook/callback endpoint. Keepz finality is observed through the BANK rail reconciliation path: execution calls KeepzProviderClient.getInstructionStatus, BankRailAdapter.reconcile maps provider status to PENDING, CONFIRMED, or FAILED, and settlement handlers update the settlement execution plus payment finality. Reconciliation findings then detect stuck pending or finality gaps from payment projection state.
Provider limitation: split-recipient finality is not represented as separate seller/platform leg finality from a callback. The current model stores one Buy Now payment intent with split metadata and can confirm or fail the overall Keepz order; seller net and Kvary commission legs remain metadata unless Keepz exposes per-split-recipient settlement status and a webhook/status adapter is added.
svc-payments with the server-side env above; keep KEEPZ_EXECUTION_MODE=DRY_RUN or sandbox unless live rails are explicitly intended.POST /api/auctions/:id/buy-now-payment and POST /api/v1/payments/buy-now-split-order.settlementFlow=KEEPZ_ECOMMERCE_SPLIT_ORDER, and custodyModel=NO_ESCROW_KEEPZ_SPLIT_ORDER.409, show the seller payout readiness failure, and create no payment intent.npm run signer:dev
svc-payments /health reports ready: true.payment_intent -> payment_pending -> payment_confirmed