{"openapi":"3.0.0","paths":{"/api/health":{"get":{"operationId":"HealthController_check","summary":"Health check","description":"Returns service health status. Use this to verify the API is reachable.","parameters":[],"responses":{"200":{"description":"Service is healthy.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"service":{"type":"string","example":"solana-pay-api"},"timestamp":{"type":"string","format":"date-time"}}}}}}},"tags":["Health"]}},"/api/merchants":{"post":{"operationId":"MerchantsController_register","summary":"Register a new merchant","description":"Self-serve registration. Returns the merchant profile, an initial API key (shown once!), and WhatsApp verification instructions. The merchant must verify via WhatsApp before the account is fully active.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterMerchantDto"}}}},"responses":{"201":{"description":"Merchant registered successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"merchant":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Kopi Kenangan"},"email":{"type":"string","example":"ops@kopikenangan.com"},"status":{"type":"string","enum":["PENDING","ACTIVE","SUSPENDED"],"example":"PENDING"},"settlementAddress":{"type":"string","nullable":true},"creditBalance":{"type":"integer","example":50}}},"apiKey":{"type":"string","description":"API key (shown once!)","example":"sk_live_a1b2c3d4e5f6..."},"verification":{"type":"object","properties":{"code":{"type":"string","example":"DUITQ-AB12CD"},"whatsappUrl":{"type":"string","description":"WhatsApp deep link for verification"},"instructions":{"type":"string"}}}}}}}},"400":{"description":"Email already registered or validation error."}},"tags":["Merchants"]}},"/api/merchants/me":{"get":{"operationId":"MerchantsController_me","summary":"Get current merchant profile","description":"Returns the authenticated merchant's profile information.","parameters":[],"responses":{"200":{"description":"Merchant profile."},"401":{"description":"Invalid or missing credentials."}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/settlement/challenge":{"get":{"operationId":"MerchantsController_challenge","summary":"Get settlement wallet challenge","description":"Returns a challenge message that must be signed with the settlement wallet's private key (Ed25519 `signMessage`). The base58-encoded signature is submitted via `PUT /api/merchants/settlement`.","parameters":[{"name":"address","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Challenge message to sign.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Solana Pay: link settlement wallet 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"}}}}}}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/settlement":{"put":{"operationId":"MerchantsController_setSettlement","summary":"Set/verify settlement wallet","description":"Links a Solana wallet as the merchant's settlement address. Requires a valid Ed25519 signature of the challenge message. The address is verified on-chain before being stored.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSettlementDto"}}}},"responses":{"200":{"description":"Settlement address linked. Returns updated merchant profile."},"400":{"description":"Invalid address, signature verification failed, or sanctioned address."}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/settlement/emoney":{"put":{"operationId":"MerchantsController_setEmoneySettlement","summary":"Set e-money settlement","description":"Switches the merchant to e-money settlement (PRD e-money-settlement v0.1). Crypto is paid to the platform pooling address and the sale amount is disbursed to the e-wallet as a withdrawal.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetEmoneySettlementDto"}}}},"responses":{"200":{"description":"E-money settlement configured. Returns updated merchant profile."},"400":{"description":"E-money settlement is not enabled or input is invalid."}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/settlement/type":{"put":{"operationId":"MerchantsController_setSettlementType","summary":"Switch active settlement method","description":"Activates CRYPTO or EMONEY settlement. The chosen method must already be configured (wallet linked for CRYPTO, e-wallet set for EMONEY).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSettlementTypeDto"}}}},"responses":{"200":{"description":"Settlement method switched. Returns updated merchant profile."},"400":{"description":"Method not configured or e-money not enabled."}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/webhook":{"put":{"operationId":"MerchantsController_setWebhook","summary":"Configure webhook URL","description":"Sets the merchant-level webhook URL. Returns a `webhookSecret` (shown once!) used to verify HMAC-SHA256 signatures on incoming webhook deliveries. Events: `invoice.paid`, `invoice.expired`, `invoice.needs_review`.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetWebhookDto"}}}},"responses":{"200":{"description":"Webhook configured. Secret shown once.","content":{"application/json":{"schema":{"type":"object","properties":{"merchant":{"type":"object","description":"Updated merchant profile"},"webhookSecret":{"type":"string","description":"HMAC secret (shown once!)","example":"whsec_abc123..."}}}}}}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/api-keys":{"post":{"operationId":"MerchantsController_createKey","summary":"Create a new API key","description":"Issues a new API key with an optional per-key callback URL. The raw key and callback secret are returned **once** and cannot be retrieved again.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyWithCallbackDto"}}}},"responses":{"201":{"description":"API key created.","content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"type":"string","description":"Raw API key (shown once!)","example":"sk_live_a1b2c3d4..."},"prefix":{"type":"string","example":"sk_live_a1b2"},"callbackUrl":{"type":"string","nullable":true},"callbackSecret":{"type":"string","nullable":true,"description":"HMAC secret for callback verification (shown once!)"}}}}}}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]},"get":{"operationId":"MerchantsController_listKeys","summary":"List API keys","description":"Returns all API keys for the authenticated merchant. Keys are masked (only prefix shown).","parameters":[],"responses":{"200":{"description":"Array of API key metadata.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"production"},"prefix":{"type":"string","example":"sk_live_a1b2"},"callbackUrl":{"type":"string","nullable":true},"lastUsedAt":{"type":"string","format":"date-time","nullable":true},"revokedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/api-keys/{id}/callback":{"put":{"operationId":"MerchantsController_setKeyCallback","summary":"Set callback URL for an API key","description":"Sets or rotates the callback (webhook) URL for a specific API key. Returns a new callback secret.","parameters":[{"name":"id","required":true,"in":"path","description":"API key UUID","schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCallbackDto"}}}},"responses":{"200":{"description":"Callback URL updated.","content":{"application/json":{"schema":{"type":"object","properties":{"callbackUrl":{"type":"string"},"callbackSecret":{"type":"string","description":"New HMAC secret (shown once!)"}}}}}}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/api-keys/{id}/revoke":{"post":{"operationId":"MerchantsController_revokeKey","summary":"Revoke an API key","description":"Permanently revokes an API key. Revoked keys cannot be used for authentication.","parameters":[{"name":"id","required":true,"in":"path","description":"API key UUID","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"Key revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"revoked":{"type":"boolean","example":true}}}}}}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/invoices":{"get":{"operationId":"MerchantsController_listInvoices","summary":"List merchant invoices (dashboard)","description":"Returns invoices for the authenticated merchant. Used by the merchant dashboard.","parameters":[],"responses":{"200":{"description":"Array of invoices."}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/withdrawals":{"get":{"operationId":"MerchantsController_listWithdrawals","summary":"List e-money withdrawals (dashboard)","description":"Returns e-money withdrawals (disbursements) for the authenticated merchant. Withdrawals are separate from sale invoices (PRD e-money-settlement v0.1).","parameters":[],"responses":{"200":{"description":"Array of withdrawals."}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/credits":{"get":{"operationId":"MerchantsController_getCredits","summary":"Get credit balance","description":"Returns the current credit balance and per-invoice cost information.","parameters":[],"responses":{"200":{"description":"Credit balance and cost info.","content":{"application/json":{"schema":{"type":"object","properties":{"balance":{"type":"integer","example":48},"costPerInvoice":{"type":"integer","example":2}}}}}}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/credits/ledger":{"get":{"operationId":"MerchantsController_creditLedger","summary":"Get credit ledger","description":"Returns the credit transaction audit trail (debits and credits).","parameters":[],"responses":{"200":{"description":"Array of credit ledger entries."}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/credits/order":{"post":{"operationId":"MerchantsController_buyCredits","summary":"Buy credits","description":"Creates a credit-purchase order. Returns a Solana Pay invoice payable to the platform wallet. Credits are granted automatically once the on-chain payment settles.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopUpCreditsDto"}}}},"responses":{"201":{"description":"Credit purchase invoice created."},"400":{"description":"Platform wallet not configured or invalid pack count."}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/invoices/{id}/signature":{"get":{"operationId":"MerchantsController_signature","summary":"Get on-chain transaction signature","description":"Returns the verified on-chain transaction signature and a Solscan explorer link for a settled invoice.","parameters":[{"name":"id","required":true,"in":"path","description":"Invoice UUID","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"Transaction signature and explorer link.","content":{"application/json":{"schema":{"type":"object","nullable":true,"properties":{"signature":{"type":"string","description":"Solana transaction signature"},"explorerUrl":{"type":"string","description":"Solscan explorer URL","example":"https://solscan.io/tx/..."}}}}}}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/invoices/{id}/resolve":{"post":{"operationId":"MerchantsController_resolve","summary":"Resolve a NEEDS_REVIEW invoice","description":"Resolves an invoice flagged as NEEDS_REVIEW (e.g. double-payment). Action: `refunded` or `dismissed`.","parameters":[{"name":"id","required":true,"in":"path","description":"Invoice UUID","schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveReviewDto"}}}},"responses":{"200":{"description":"Invoice resolved.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","example":"REFUNDED"}}}}}}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/settlement/ata":{"get":{"operationId":"MerchantsController_ataStatus","summary":"Check ATA status","description":"Returns the Associated Token Account (ATA) status for each supported token mint on the merchant's settlement wallet. An ATA must exist before the merchant can receive payments in that token.","parameters":[],"responses":{"200":{"description":"Array of ATA status objects per token."}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]},"post":{"operationId":"MerchantsController_provisionAta","summary":"Provision missing ATAs","description":"Creates any missing Associated Token Accounts for the merchant's settlement wallet. The creation fee is subsidized by the platform (when a funder wallet is configured).","parameters":[],"responses":{"201":{"description":"ATA provisioning results per token."}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/merchants/invoices.csv":{"get":{"operationId":"MerchantsController_invoicesCsv","summary":"Export invoices as CSV","description":"Downloads all invoices for the authenticated merchant as a CSV file.","parameters":[],"responses":{"200":{"description":"CSV file download."}},"tags":["Merchants"],"security":[{"SessionJwt":[]},{"ApiKey":[]}]}},"/api/auth/otp/request":{"post":{"operationId":"AuthController_request","summary":"Request OTP login code","description":"Sends a one-time login code to the merchant's verified WhatsApp number. Always returns `{ sent: true }` regardless of whether the email exists (to prevent email enumeration).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestOtpDto"}}}},"responses":{"201":{"description":"OTP sent (or silently ignored if email not found).","content":{"application/json":{"schema":{"type":"object","properties":{"sent":{"type":"boolean","example":true}}}}}}},"tags":["Auth"]}},"/api/auth/otp/verify":{"post":{"operationId":"AuthController_verify","summary":"Verify OTP and get session token","description":"Verifies the OTP code and returns a JWT session token for dashboard access. Use this token as `Authorization: Bearer <token>` on merchant dashboard endpoints.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyOtpDto"}}}},"responses":{"201":{"description":"OTP verified. Returns JWT token and merchant info.","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"JWT session token","example":"eyJhbGciOiJIUzI1NiIs..."},"merchant":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Kopi Kenangan"},"email":{"type":"string","example":"ops@kopikenangan.com"}}}}}}}},"400":{"description":"Invalid or expired OTP code."}},"tags":["Auth"]}},"/api/health/rpc":{"get":{"operationId":"RpcHealthController_rpcHealth","summary":"Solana RPC provider health","description":"Returns health status for all configured Solana RPC providers, including failover status.","parameters":[],"responses":{"200":{"description":"RPC provider health snapshot.","content":{"application/json":{"schema":{"type":"object","properties":{"providers":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"healthy":{"type":"boolean"},"latencyMs":{"type":"number","nullable":true}}}}}}}}}},"tags":["Health"]}},"/api/health/funder":{"get":{"operationId":"RpcHealthController_funder","summary":"ATA funder wallet status","description":"Returns the ATA funder hot-wallet balance and low-balance alert status. The funder subsidizes token account creation for merchants.","parameters":[],"responses":{"200":{"description":"Funder wallet status.","content":{"application/json":{"schema":{"type":"object","properties":{"configured":{"type":"boolean","description":"Whether a funder wallet is configured"},"address":{"type":"string","nullable":true,"description":"Funder wallet address (base58)"},"balanceSol":{"type":"number","nullable":true,"description":"Balance in SOL"},"low":{"type":"boolean","description":"Whether the balance is below the low threshold"}}}}}}},"tags":["Health"]}},"/api/admin/login":{"post":{"operationId":"AdminController_login","summary":"Admin login","description":"Authenticates with admin credentials (configured via environment variables) and returns a JWT session token.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminLoginDto"}}}},"responses":{"201":{"description":"Admin authenticated.","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Admin JWT token","example":"eyJhbGciOiJIUzI1NiIs..."}}}}}},"401":{"description":"Invalid admin credentials."}},"tags":["Admin"]}},"/api/admin/platform":{"get":{"operationId":"AdminController_platform","summary":"Get platform credit wallet (public)","description":"Returns the platform credit-settlement wallet address. Public for transparency/verification.","parameters":[],"responses":{"200":{"description":"Platform wallet address.","content":{"application/json":{"schema":{"type":"object","properties":{"creditSettlementAddress":{"type":"string","nullable":true,"example":"7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"}}}}}}},"tags":["Admin"]}},"/api/admin/settings":{"get":{"operationId":"AdminController_getSettings","summary":"Get platform settings","description":"Returns current platform settings. Requires admin authentication.","parameters":[],"responses":{"200":{"description":"Platform settings."},"401":{"description":"Invalid or missing admin token."}},"tags":["Admin"],"security":[{"AdminJwt":[]}]}},"/api/admin/credit-wallet":{"put":{"operationId":"AdminController_setCreditWallet","summary":"Set platform credit wallet","description":"Sets the Solana wallet address that receives credit-purchase payments from merchants.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCreditWalletDto"}}}},"responses":{"200":{"description":"Credit wallet updated.","content":{"application/json":{"schema":{"type":"object","properties":{"creditSettlementAddress":{"type":"string","example":"7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"}}}}}},"401":{"description":"Invalid or missing admin token."}},"tags":["Admin"],"security":[{"AdminJwt":[]}]}},"/api/admin/overview":{"get":{"operationId":"AdminController_overview","summary":"Platform overview stats","parameters":[],"responses":{"200":{"description":"Aggregate platform counters + e-money config."}},"tags":["Admin"],"security":[{"AdminJwt":[]}]}},"/api/admin/merchants":{"get":{"operationId":"AdminController_merchants","summary":"List merchants","parameters":[{"name":"query","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Array of merchants."}},"tags":["Admin"],"security":[{"AdminJwt":[]}]}},"/api/admin/merchants/{id}/status":{"post":{"operationId":"AdminController_setMerchantStatus","summary":"Set a merchant status (activate/suspend)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetMerchantStatusDto"}}}},"responses":{"200":{"description":"Updated merchant status."}},"tags":["Admin"],"security":[{"AdminJwt":[]}]}},"/api/admin/invoices":{"get":{"operationId":"AdminController_invoices","summary":"List invoices (platform-wide)","parameters":[{"name":"query","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Array of invoices."}},"tags":["Admin"],"security":[{"AdminJwt":[]}]}},"/api/admin/withdrawals":{"get":{"operationId":"AdminController_withdrawals","summary":"List e-money withdrawals (platform-wide)","parameters":[{"name":"query","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Array of withdrawals."}},"tags":["Admin"],"security":[{"AdminJwt":[]}]}},"/api/admin/withdrawals/{id}/retry":{"post":{"operationId":"AdminController_retryWithdrawal","summary":"Retry a failed/pending withdrawal disbursement","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Withdrawal re-queued."}},"tags":["Admin"],"security":[{"AdminJwt":[]}]}},"/api/invoices":{"post":{"operationId":"InvoicesController_create","summary":"Create an invoice","description":"Creates a new payment invoice. Returns the invoice with Solana Pay URLs and QR code data URLs for each accepted token. The payer scans the QR code with a Solana-compatible wallet to pay.\n\nConsumes **1 credit** on creation; an additional credit is consumed when the invoice is settled.\n\n**Idempotency:** The `(merchantId, orderId)` pair is unique. Re-using the same `orderId` will return an error.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvoiceDto"}}}},"responses":{"201":{"description":"Invoice created successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"b1c2d3e4-5678-90ab-cdef-1234567890ab"},"merchantId":{"type":"string","format":"uuid"},"orderId":{"type":"string","example":"ORD-20240115-001"},"amount":{"type":"number","example":12.5},"pricingCurrency":{"type":"string","enum":["USD","IDR"],"example":"USD"},"amountFiat":{"type":"number","example":12.5},"amountToken":{"type":"number","example":12.5},"fxRate":{"type":"number","nullable":true,"example":null},"description":{"type":"string","nullable":true,"example":"2x Americano"},"acceptedTokens":{"type":"array","items":{"type":"string","enum":["USDC","USDT"]}},"status":{"type":"string","example":"PENDING"},"reference":{"type":"string","description":"Solana Pay reference public key (base58)"},"expiresAt":{"type":"string","format":"date-time"},"paidAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"payments":{"type":"array","description":"Solana Pay URLs and QR codes per accepted token.","items":{"type":"object","properties":{"token":{"type":"string","enum":["USDC","USDT"]},"url":{"type":"string","description":"Solana Pay transfer request URL"},"qr":{"type":"string","description":"Base64 PNG data URL of the QR code"}}}}}}}}},"400":{"description":"Validation error, insufficient credits, or missing settlement address."},"401":{"description":"Invalid or missing API key."}},"tags":["Invoices"],"security":[{"ApiKey":[]}]},"get":{"operationId":"InvoicesController_findAll","summary":"List invoices","description":"Returns the most recent 100 invoices for the authenticated merchant, sorted by creation date (newest first).","parameters":[],"responses":{"200":{"description":"Array of invoice objects."},"401":{"description":"Invalid or missing API key."}},"tags":["Invoices"],"security":[{"ApiKey":[]}]}},"/api/cashier/invoices":{"post":{"operationId":"InvoicesController_createForCashier","summary":"Create invoice (cashier/POS mode)","description":"Same as `POST /api/invoices` but semantically intended for point-of-sale / cashier terminals. Identical request and response format.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvoiceDto"}}}},"responses":{"201":{"description":"Invoice created successfully (same shape as POST /api/invoices)."},"400":{"description":"Validation error, insufficient credits, or missing settlement address."},"401":{"description":"Invalid or missing API key."}},"tags":["Invoices"],"security":[{"ApiKey":[]}]}},"/api/invoices/{id}":{"get":{"operationId":"InvoicesController_findOne","summary":"Get invoice details (public)","description":"Retrieve a single invoice by ID. This endpoint is **public** (no auth required) so checkout pages can poll for status updates. Includes Solana Pay URLs and QR data when the invoice is still PENDING.","parameters":[{"name":"id","required":true,"in":"path","description":"Invoice UUID","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"Invoice details."},"404":{"description":"Invoice not found."}},"tags":["Invoices"]}},"/api/invoices/{id}/qr":{"get":{"operationId":"InvoicesController_qr","summary":"Get QR code image (public)","description":"Returns a Solana Pay QR code image for the specified invoice and token. Supports PNG (default) and SVG format. The payer scans this QR with any Solana-compatible wallet.","parameters":[{"name":"id","required":true,"in":"path","description":"Invoice UUID","schema":{"format":"uuid","type":"string"}},{"name":"token","required":true,"in":"query","description":"Token to generate the QR for.","schema":{"enum":["USDC","USDT"],"type":"string"}},{"name":"format","required":false,"in":"query","description":"Image format.","schema":{"enum":["png","svg"],"type":"string"}}],"responses":{"200":{"description":"QR code image (PNG or SVG)."},"400":{"description":"Unknown token or invoice does not accept the specified token."},"404":{"description":"Invoice not found."}},"tags":["Invoices"]}},"/api/fx/quote":{"get":{"operationId":"FxController_quote","summary":"Get live FX quote","description":"Returns a live foreign-exchange rate quote for converting fiat (IDR) to stablecoin (USDT/USDC). USD is pegged 1:1 (no provider call). Useful for showing a real-time price preview before invoice creation.","parameters":[{"name":"fiat","required":false,"in":"query","description":"Source fiat currency.","schema":{"enum":["IDR","USD"],"type":"string"}},{"name":"token","required":false,"in":"query","description":"Target stablecoin.","schema":{"enum":["USDC","USDT"],"type":"string"}}],"responses":{"200":{"description":"FX quote.","content":{"application/json":{"schema":{"type":"object","properties":{"fiat":{"type":"string","enum":["IDR","USD"],"example":"IDR"},"token":{"type":"string","enum":["USDC","USDT"],"example":"USDT"},"midRate":{"type":"number","description":"Mid-market rate (fiat per 1 token)","example":15850.25},"effectiveRate":{"type":"number","description":"Rate after platform spread","example":15691.75},"spreadBps":{"type":"integer","description":"Spread in basis points","example":100},"source":{"type":"string","example":"coingecko"},"fetchedAt":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Unsupported token or currency."}},"tags":["FX"]}},"/api/kyc/session":{"post":{"operationId":"KycController_createSession","summary":"Start an identity-verification session","parameters":[],"responses":{"201":{"description":""}},"tags":["KYC"],"security":[{"SessionJwt":[]}]}},"/api/kyc/status":{"get":{"operationId":"KycController_status","summary":"Get identity-verification status","parameters":[],"responses":{"200":{"description":""}},"tags":["KYC"],"security":[{"SessionJwt":[]}]}},"/api/kyc/webhook":{"post":{"operationId":"KycController_webhook","summary":"Didit verification webhook (signed)","parameters":[{"name":"x-signature","required":true,"in":"header","schema":{"type":"string"}},{"name":"x-timestamp","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["KYC"]}}},"info":{"title":"DuitQ — Crypto QR Payment API","description":"DuitQ is a **non-custodial** crypto QR payment gateway built on the Solana blockchain using the [Solana Pay](https://solanapay.com) standard.\n\nMerchants accept **USDC** and **USDT** stablecoin payments via QR codes. Funds flow directly from the payer's wallet to the merchant's settlement wallet on-chain — no smart contracts, no custody.\n\n## Authentication\n\nMost endpoints require an **API key** passed as a Bearer token:\n```\nAuthorization: Bearer sk_live_…\n```\nAlternatively, use the `X-API-Key` header.\n\nDashboard endpoints accept a **JWT session token** obtained via OTP login (`POST /api/auth/otp/request` → `POST /api/auth/otp/verify`).\n\n## Quick Start\n\n1. **Register** → `POST /api/merchants` (returns API key)\n2. **Link wallet** → `GET /api/merchants/settlement/challenge` + `PUT /api/merchants/settlement`\n3. **Create invoice** → `POST /api/invoices` (returns QR code)\n4. **Track payment** → `GET /api/invoices/:id` or WebSocket at `/ws`\n5. **Receive webhooks** → `PUT /api/merchants/webhook` to configure\n\n## Resources\n\n- [Integration Guide](/docs/INTEGRATION.md)\n- [OpenAPI JSON](/api/docs-json)\n","version":"1.0.0","contact":{"name":"DuitQ Team","url":"https://duitq.io","email":"dev@duitq.io"},"license":{"name":"MIT","url":"https://opensource.org/licenses/MIT"}},"tags":[{"name":"Invoices","description":"Create and manage payment invoices. Core integration surface."},{"name":"Merchants","description":"Merchant registration, wallet setup, and account management."},{"name":"Auth","description":"OTP-based merchant login for the dashboard."},{"name":"FX","description":"Live foreign-exchange rate quotes (IDR → USDT/USDC)."},{"name":"Health","description":"System health checks and diagnostics."},{"name":"Admin","description":"Platform administration (requires admin credentials)."},{"name":"Webhooks","description":"Inbound WhatsApp webhook for merchant verification."}],"servers":[{"url":"http://localhost:3000","description":"Local development"},{"url":"https://api.duitq.io","description":"Production"}],"components":{"securitySchemes":{"ApiKey":{"scheme":"bearer","bearerFormat":"API Key","type":"http","description":"Merchant API key (`sk_live_…`). Obtained at registration."},"SessionJwt":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"JWT session token obtained via OTP login (`POST /api/auth/otp/verify`)."},"AdminJwt":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"Admin JWT obtained via `POST /api/admin/login`."}},"schemas":{"RegisterMerchantDto":{"type":"object","properties":{"name":{"type":"string","description":"Business or merchant display name.","example":"Kopi Kenangan","maxLength":255},"email":{"type":"string","description":"Merchant email address. Must be unique across the platform.","example":"ops@kopikenangan.com","maxLength":320}},"required":["name","email"]},"SetSettlementDto":{"type":"object","properties":{"address":{"type":"string","description":"Solana wallet address (base58) to receive settlement payments.","example":"7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU","maxLength":64},"signature":{"type":"string","description":"Base58-encoded Ed25519 signature of the challenge message. Obtain the challenge from `GET /api/merchants/settlement/challenge?address=<wallet>`.","example":"4yZ8...base58sig"}},"required":["address","signature"]},"SetEmoneySettlementDto":{"type":"object","properties":{"provider":{"type":"string","description":"E-wallet provider to receive settlements.","enum":["DANA","OVO","GOPAY","SHOPEEPAY"],"example":"DANA"},"account":{"type":"string","description":"E-wallet account number (typically the registered phone number).","example":"081234567890","maxLength":64},"name":{"type":"string","description":"Account holder name.","example":"Kopi Kenangan","maxLength":255}},"required":["provider","account"]},"SetSettlementTypeDto":{"type":"object","properties":{"type":{"type":"string","description":"Which settlement method to activate.","enum":["CRYPTO","EMONEY"],"example":"EMONEY"}},"required":["type"]},"SetWebhookDto":{"type":"object","properties":{"url":{"type":"string","description":"HTTPS URL where DuitQ will POST webhook events (invoice.paid, invoice.expired, etc.).","example":"https://myapp.com/webhooks/duitq","maxLength":2048}},"required":["url"]},"CreateApiKeyWithCallbackDto":{"type":"object","properties":{"name":{"type":"string","description":"A label for this API key.","example":"pos-terminal-1","maxLength":255},"callbackUrl":{"type":"string","description":"Per-key callback URL. If set, webhook events for invoices created with this key will be sent here instead of the merchant-level webhook.","example":"https://myapp.com/webhooks/pos-1","maxLength":2048}},"required":["name"]},"SetCallbackDto":{"type":"object","properties":{"callbackUrl":{"type":"string","description":"Callback (webhook) URL for this specific API key.","example":"https://myapp.com/webhooks/key-callback","maxLength":2048}},"required":["callbackUrl"]},"TopUpCreditsDto":{"type":"object","properties":{"packs":{"type":"number","description":"Number of credit packs to buy. Each pack contains CREDIT_PACK_SIZE credits (default 100).","example":1,"minimum":1,"maximum":10000}},"required":["packs"]},"ResolveReviewDto":{"type":"object","properties":{"action":{"type":"string","description":"Resolution action: \"refunded\" marks the invoice as REFUNDED, \"dismissed\" clears the review flag.","enum":["refunded","dismissed"],"example":"refunded"},"note":{"type":"string","description":"Optional note explaining the resolution (e.g. refund TX hash).","example":"Refunded via Solana TX 3xABC...","maxLength":1000}},"required":["action"]},"RequestOtpDto":{"type":"object","properties":{"email":{"type":"string","description":"Merchant email address to send the OTP login code to (via WhatsApp).","example":"ops@kopikenangan.com"}},"required":["email"]},"VerifyOtpDto":{"type":"object","properties":{"email":{"type":"string","description":"Merchant email address.","example":"ops@kopikenangan.com"},"code":{"type":"string","description":"One-time password received via WhatsApp.","example":"482910","maxLength":8}},"required":["email","code"]},"AdminLoginDto":{"type":"object","properties":{"username":{"type":"string","description":"Admin username (configured via ADMIN_USERNAME env var).","example":"admin","maxLength":128},"password":{"type":"string","description":"Admin password (configured via ADMIN_PASSWORD env var).","example":"change-me-admin","maxLength":256}},"required":["username","password"]},"SetCreditWalletDto":{"type":"object","properties":{"address":{"type":"string","description":"Solana wallet address (base58) that receives credit-purchase payments.","example":"7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU","maxLength":64}},"required":["address"]},"SetMerchantStatusDto":{"type":"object","properties":{"status":{"type":"string","description":"New merchant status.","enum":["PENDING","ACTIVE","SUSPENDED"],"example":"SUSPENDED"}},"required":["status"]},"CreateInvoiceDto":{"type":"object","properties":{"orderId":{"type":"string","description":"Your unique order/reference ID for this invoice.","example":"ORD-20240115-001","maxLength":128},"amount":{"type":"number","description":"Amount in the pricing currency. For USD this is the dollar amount (e.g. 12.50). For IDR it is the rupiah nominal (converted to USDT/USDC at creation time).","example":12.5,"minimum":0.01,"maximum":1000000000},"pricingCurrency":{"type":"string","description":"Currency the invoice is priced in. Defaults to USD.","enum":["USD","IDR"],"default":"USD","example":"USD"},"description":{"type":"string","description":"Human-readable description shown to the payer.","example":"2x Americano, 1x Croissant","maxLength":512},"acceptedTokens":{"type":"array","description":"Tokens the invoice accepts. Defaults to all configured tokens (USDC, USDT).","example":["USDC","USDT"],"items":{"type":"string","enum":["USDC","USDT"]}},"ttlMinutes":{"type":"number","description":"Invoice validity window in minutes. Defaults to 15. Max 1440 (24h).","example":15,"minimum":1,"maximum":1440,"default":15},"redirectUrl":{"type":"string","description":"URL to redirect the payer to after successful payment.","example":"https://myshop.com/order/ORD-001/thank-you","maxLength":2048}},"required":["orderId","amount"]}}}}