Error Handling
Understanding how to handle errors properly ensures a robust integration with the DVPay API. This guide covers error response formats, common error codes, and best practices for error recovery.
Error Response Format
All DVPay API errors follow a consistent JSON structure:
{
"error": {
"code": "insufficient_funds",
"message": "The merchant account has insufficient funds to process this payout",
"details": {
"available_balance": 50.00,
"required_amount": 100.00
},
"request_id": "req_abc123xyz",
"timestamp": 1696435200
}
}
HTTP Status Codes
DVPay API uses standard HTTP status codes:
| Status Code | Meaning | Common Causes |
|---|---|---|
400 | Bad Request | Invalid request parameters, malformed JSON |
401 | Unauthorized | Missing or invalid API credentials |
403 | Forbidden | Valid credentials but insufficient permissions |
404 | Not Found | Resource (order, transaction) doesn't exist |
409 | Conflict | Duplicate order ID, conflicting operation |
422 | Unprocessable Entity | Valid JSON but business logic validation failed |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server-side issue (safe to retry) |
503 | Service Unavailable | Temporary maintenance or overload |
Common Error Codes
Authentication Errors
HTTP Status: 401 Unauthorized
Cause: API key, App ID, or timestamp is invalid
Solution:
- Verify your API credentials in the merchant dashboard
- Ensure
X-Timestampis current (within 5 minutes) - Check for typos in header names
// Correct header format
headers: {
'X-App-Id': 'your-app-id',
'X-Api-Key': 'your-api-key',
'X-Timestamp': Math.floor(Date.now() / 1000).toString()
}
HTTP Status: 401 Unauthorized
Cause: The X-Timestamp header is too old or in the future
Solution: Use current server time within ±5 minutes
req.Header.Set("X-Timestamp", fmt.Sprintf("%d", time.Now().Unix()))
HTTP Status: 403 Forbidden
Cause: Your merchant account has been suspended
Solution: Contact DVPay support to resolve account issues
Validation Errors
HTTP Status: 422 Unprocessable Entity
Cause: Amount is negative, zero, or exceeds limits
Solution:
- Ensure amount is positive
- Check minimum: $0.10 USD or 400 KHR
- Check maximum: $10,000 USD per transaction
{
"error": {
"code": "invalid_amount",
"message": "Amount must be between $0.10 and $10,000 USD",
"details": {
"provided_amount": 0.05,
"min_amount": 0.10,
"max_amount": 10000.00
}
}
}
HTTP Status: 422 Unprocessable Entity
Cause: Unsupported currency code
Solution: Use only USD or KHR
// Valid currencies
const validCurrencies = ['USD', 'KHR'];
HTTP Status: 404 Not Found
Cause: Store ID doesn't exist or doesn't belong to your merchant account
Solution: Verify store ID in your merchant dashboard
Business Logic Errors
HTTP Status: 409 Conflict
Cause: Attempting to pay an order that's already been paid
Solution: Check order status before processing payment
// Always verify order status first
orderStatus, _ := getOrderStatus(orderID)
if orderStatus == "paid" {
return errors.New("order already paid")
}
HTTP Status: 422 Unprocessable Entity
Cause: Order is not in a refundable state (pending, cancelled, or already refunded)
Solution: Only refund completed/paid orders
{
"error": {
"code": "order_cannot_be_refunded",
"message": "Only paid orders can be refunded",
"details": {
"order_status": "cancelled"
}
}
}
HTTP Status: 422 Unprocessable Entity
Cause: Merchant account lacks sufficient balance for payout
Solution: Check available balance before initiating payouts
const balance = await getMerchantBalance();
if (balance < payoutAmount) {
throw new Error('Insufficient merchant balance');
}
HTTP Status: 409 Conflict
Cause: Order ID already exists in the system
Solution: Use unique order IDs (UUIDs recommended)
import "github.com/google/uuid"
orderID := uuid.New().String() // Generates unique ID
Rate Limiting
HTTP Status: 429 Too Many Requests
Cause: Exceeded API rate limits (100 requests per minute per merchant)
Solution: Implement exponential backoff retry logic
async function callAPIWithRetry(apiCall, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await apiCall();
} catch (error) {
if (error.status === 429) {
const retryAfter = error.headers['Retry-After'] || Math.pow(2, i);
await sleep(retryAfter * 1000);
continue;
}
throw error;
}
}
}
Response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1696435260
Retry-After: 60
Error Handling Best Practices
1. Implement Retry Logic
Retry transient errors with exponential backoff:
func retryableRequest(fn func() error, maxRetries int) error {
retryableErrors := map[int]bool{
429: true, // Rate limit
500: true, // Internal server error
503: true, // Service unavailable
}
for attempt := 0; attempt < maxRetries; attempt++ {
err := fn()
if err == nil {
return nil
}
// Check if error is retryable
if httpErr, ok := err.(*HTTPError); ok {
if retryableErrors[httpErr.StatusCode] {
backoff := time.Duration(math.Pow(2, float64(attempt))) * time.Second
time.Sleep(backoff)
continue
}
}
return err // Non-retryable error
}
return errors.New("max retries exceeded")
}
async function retryableRequest(apiCall, maxRetries = 3) {
const retryableStatuses = [429, 500, 503];
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await apiCall();
} catch (error) {
if (retryableStatuses.includes(error.response?.status)) {
const backoff = Math.pow(2, attempt) * 1000;
console.log(`Retrying after ${backoff}ms...`);
await new Promise(resolve => setTimeout(resolve, backoff));
continue;
}
throw error; // Non-retryable error
}
}
throw new Error('Max retries exceeded');
}
// Usage
try {
const order = await retryableRequest(() => createOrder(data));
} catch (error) {
console.error('Failed to create order:', error);
}
2. Log Errors with Context
Always log errors with request IDs for debugging:
function logError(error, context) {
console.error({
timestamp: new Date().toISOString(),
request_id: error.request_id,
error_code: error.code,
message: error.message,
context: context,
details: error.details
});
}
// Usage
try {
await createOrder(orderData);
} catch (error) {
logError(error, {
action: 'create_order',
order_id: orderData.order_id
});
}
3. Handle Errors Gracefully
Provide user-friendly error messages:
func handleAPIError(err error) string {
apiErr, ok := err.(*APIError)
if !ok {
return "An unexpected error occurred. Please try again."
}
switch apiErr.Code {
case "insufficient_funds":
return "Unable to process payment. Please contact support."
case "invalid_amount":
return "Invalid payment amount. Please check and try again."
case "rate_limit_exceeded":
return "Too many requests. Please wait a moment and try again."
default:
return apiErr.Message
}
}
function getUserFriendlyMessage(error) {
const messages = {
'insufficient_funds': 'Payment cannot be processed at this time.',
'invalid_amount': 'Invalid payment amount. Please check and try again.',
'rate_limit_exceeded': 'Too many requests. Please wait and try again.',
'order_already_paid': 'This order has already been paid.',
};
return messages[error.code] || 'An error occurred. Please try again.';
}
// Display to user
catch (error) {
const friendlyMessage = getUserFriendlyMessage(error);
showNotification(friendlyMessage, 'error');
}
4. Monitor Error Rates
Track error rates and set up alerts:
const errorMetrics = {
total: 0,
by_code: {},
by_status: {}
};
function trackError(error) {
errorMetrics.total++;
errorMetrics.by_code[error.code] = (errorMetrics.by_code[error.code] || 0) + 1;
errorMetrics.by_status[error.status] = (errorMetrics.by_status[error.status] || 0) + 1;
// Alert if error rate exceeds threshold
if (errorMetrics.total > 100) {
sendAlert('High error rate detected', errorMetrics);
}
}
Idempotency
Prevent duplicate operations by using idempotency keys:
POST /api/v1/payment-gateway/order/create
X-Idempotency-Key: unique-key-123
DVPay stores the result of the first request and returns the same response for subsequent requests with the same idempotency key within 24 hours.
import "github.com/google/uuid"
func createOrderWithIdempotency(payload OrderRequest) (*OrderResponse, error) {
idempotencyKey := uuid.New().String()
req.Header.Set("X-Idempotency-Key", idempotencyKey)
// Safe to retry - same idempotency key returns same result
return callAPI(req)
}
const { v4: uuidv4 } = require('uuid');
async function createOrder(orderData) {
const idempotencyKey = uuidv4();
const response = await axios.post(url, orderData, {
headers: {
'X-Idempotency-Key': idempotencyKey,
...authHeaders
}
});
return response.data;
}
Troubleshooting Checklist
Verify API Credentials
- Check
X-App-IdandX-Api-Keyare correct - Ensure credentials are from production (not sandbox)
Check Request Format
- Validate JSON syntax
- Ensure all required fields are present
- Verify data types match API specification
Review Timestamp
- Use current server time (±5 minutes tolerance)
- Format as Unix timestamp in seconds
Test Connectivity
- Ensure your server can reach
https://merchant.dv.vai247.pro - Check firewall rules and SSL certificate validation
Inspect Response
- Log full error response including
request_id - Check HTTP status code and error code
- Review error details for specific guidance
Getting Help
If you encounter persistent errors:
- Check Status Page: Visit status.dvpay.com for service status
- Review Logs: Include
request_idfrom error responses - Contact Support: Email support@dvpay.com with:
- Request ID
- Timestamp of the error
- Full error response
- Steps to reproduce
request_id from error responses when contacting support for faster resolution.