Appearance
Payins
Merchants call this endpoint to request a payin. The portal forwards the request to the configured payin backend to generate the QR code or payment flow, then returns the backend’s response.
IMPORTANT - Amount Mismatch Handling
Users manually enter payment amounts, so the actual paid amount may differ from your requested amount. Your system must handle overpayments and underpayments correctly. See Amount Handling for details.
Endpoint
http
POST /api/v1/payins1
All payin traffic flows through this unified endpoint. The application forwards every request to the remote server configured via PAYIN_SERVER_* environment variables, using the exact JSON payload supplied by the client.
Authentication
Requires API key authentication via the X-API-Key (or Api-Key) header.
Request Body
json
{
"amount": 100.50,
"currency": "USDT",
"memo": "Payment for invoice #123",
"out_trade_no": "MERCHANT-ORDER-001"
}1
2
3
4
5
6
2
3
4
5
6
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Amount in USDT (min 0.01, up to 6 decimal places). |
currency | string | No | Defaults to USDT. Supported: USDT. |
memo | string | No | Optional reference or note (max 255 characters). |
out_trade_no | string | Yes | Merchant order identifier (max 255 characters). Must be unique per merchant. |
The payload is forwarded as-is to the configured payin server. Set
PAYIN_SERVER_ENDPOINTandPAYIN_SERVER_API_KEYin your environment to enable forwarding.
Response
json
{
"success": true,
"code": 200,
"data": {
"invoice_reference": "PAYIN-202511A1B2C3D4E5F6G7H",
"amount": 100.5,
"currency": "USDT",
"fee": 2.50,
"qrcode": {
"content": "00020101021229...",
"image": "iVBORw0KGgoAAAANSUhEUgAAAyoAAANmCAIAAACaMeiHAADFN0lEQVR4nOzdd..."
},
"out_trade_no": "MERCHANT-ORDER-001"
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| Field | Description |
|---|---|
invoice_reference | System-generated unique invoice reference (format: PAYIN-XXXXXXXXXXXXXXXXXX). |
amount | The requested amount. |
currency | Currency code (defaults to USDT). |
fee | Processing fee charged for this transaction (in same currency as amount). |
qrcode | Object containing the QR code data. |
qrcode.content | Decoded QR code data (e.g., payment URI for deep linking). |
qrcode.image | Base64-encoded PNG image of the QR code. |
out_trade_no | Merchant's order identifier. |
Example Request (cURL)
bash
#!/bin/bash
API_KEY="your_api_key"
curl -X POST "https://dev-crypto-portal.kesspay.io/api/v1/payins" \
-H "Content-Type: application/json" \
-H "X-Api-Key: ${API_KEY}" \
-d '{
"amount": 100,
"memo": "POS-INV-9231",
"out_trade_no": "ORDER-2025-001"
}'1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
How it works
- Merchant calls
/api/v1/payinswith amount,out_trade_no, and optional memo. - Portal validates uniqueness of
out_trade_no(must be unique per merchant). - Portal creates a TopUp record and forwards the JSON payload to your configured payin server.
- The payin server generates the QR code/payment payload and returns JSON.
- Portal returns the TopUp details, fee information, and QR code to the merchant.
- When the payment is settled, the webhook callback includes the
out_trade_nofor merchant reconciliation.
Payment Expiration
5-Minute Expiration
Payin requests expire after 5 minutes from creation. If the user does not complete payment within this window, the payin will be marked as expired and a payin.expired webhook will be sent.
To check if a payment has expired, use the Query Payment Status endpoint or handle the payin.expired webhook event.
Amount Handling
IMPORTANT - Read Carefully
Since users manually enter the payment amount (free-form input), the actual amount paid may differ from the requested amount. Your system must handle this correctly.
The system credits the actual paid amount to your wallet, regardless of what was originally requested:
| Scenario | Behavior |
|---|---|
| Exact amount | Payment settles normally, full amount credited |
| Overpayment | Full paid amount is credited (e.g., requested 100, paid 150 → 150 credited) |
| Underpayment | Full paid amount is credited (e.g., requested 100, paid 80 → 80 credited) |
Webhook Fields for Amount Mismatch
When an amount mismatch occurs, the webhook includes additional fields:
original_amount- The amount you originally requestedpayment_match_status- One of:exact,overpaid,underpaid
See Webhooks - Amount Mismatch Handling for complete details.
Always Use the Webhook Amount
Never assume the credited amount equals your requested amount. Always use the amount field from the webhook to determine the actual credited amount.
Error Responses
| Status | Message | When it occurs |
|---|---|---|
| 422 | Validation errors for fields or duplicate out_trade_no. | Missing/invalid fields or out_trade_no already exists for this merchant. |
| 502 | Payin service is unavailable. Please try again later. | The upstream payin server responded with a 4xx/5xx or could not be reached. |
| 500 | Unable to process payin request. Please contact support. | Unexpected exception while forwarding the request. |
Query Payment Status
Query the status of a payment using your merchant order reference.
Endpoint
http
POST /api/v1/payins/status1
Authentication
Requires API key authentication via the X-API-Key header.
Request Body
json
{
"out_trade_no": "MERCHANT-ORDER-001"
}1
2
3
2
3
| Field | Type | Required | Description |
|---|---|---|---|
out_trade_no | string | Yes | Your merchant order identifier used when creating the payin. |
Response
json
{
"success": true,
"code": 200,
"data": {
"trx_ref": "TRX-PROVIDER-123",
"amount": 100.5,
"fee": 2.5,
"invoice_reference": "PAYIN-202511A1B2C3D4E5F6G7H",
"out_trade_no": "MERCHANT-ORDER-001",
"status": "success"
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
| Field | Type | Description |
|---|---|---|
trx_ref | string | Provider transaction reference. |
amount | number | Payment amount (same as input). |
fee | number | Fee amount. |
invoice_reference | string | System-generated invoice reference. |
out_trade_no | string | Your merchant order identifier. |
status | string | Current payment status. |
Status Values
| Status | Description |
|---|---|
waiting | Payment pending, awaiting user payment. |
success | Payment confirmed and wallet credited. |
expired | Payment expired without completion. |
close | Payment closed/cancelled. |
Example Request (cURL)
bash
#!/bin/bash
API_KEY="your_api_key"
curl -X POST "https://dev-crypto-portal.kesspay.io/api/v1/payins/status" \
-H "Content-Type: application/json" \
-H "X-Api-Key: ${API_KEY}" \
-d '{
"out_trade_no": "MERCHANT-ORDER-001"
}'1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Error Responses
| Status | Message | When it occurs |
|---|---|---|
| 401 | Unauthorized | Invalid or missing API key. |
| 404 | Top-up not found. | No payment found for the given out_trade_no. |
| 422 | Validation error | Missing out_trade_no field. |
Implementation Notes
- The request payload is never mutated. If you include optional fields, they are forwarded to the upstream server.
- Payin server configuration lives in
config/services.php(PAYIN_SERVER_ENDPOINT,PAYIN_SERVER_API_KEY,PAYIN_SERVER_TIMEOUT). - If
PAYIN_SERVER_ENDPOINTis not set, payin requests will fail validation. Configure your own provider endpoint and API key before enabling payins.
Next Steps
- Learn about Webhooks to receive payment notifications from the portal.
- Review Rate Limiting to understand API throttling rules.