Currencies & Conversion
DVPay supports multi-currency transactions with US Dollars (USD) and Cambodian Riel (KHR). This guide covers currency handling, conversion rules, and best practices for international payments.
Supported Currencies
DVPay currently supports two currencies:
USD - US Dollar
Standard international currency. Minimum: $0.10
KHR - Cambodian Riel
Local Cambodian currency. Minimum: 400 KHR
Currency Specification
When creating orders, specify the currency using ISO 4217 codes:
{
"store_id": "store-123",
"amount": 100.00,
"currency": "USD" // or "KHR"
}
USD, KHRAmount Limits
Each currency has minimum and maximum transaction limits:
| Currency | Minimum | Maximum | Decimal Places |
|---|---|---|---|
| USD | $0.10 | $10,000 | 2 |
| KHR | ៛400 | ៛40,000,000 | 0 |
Creating Orders by Currency
USD Orders
func createUSDOrder(storeID string, amount float64) (*OrderResponse, error) {
// Validate USD amount
if amount < 0.10 {
return nil, errors.New("minimum USD amount is $0.10")
}
if amount > 10000.00 {
return nil, errors.New("maximum USD amount is $10,000")
}
// Round to 2 decimal places
amount = math.Round(amount*100) / 100
payload := CreateOrderRequest{
StoreID: storeID,
Amount: amount,
Currency: "USD",
}
return createOrder(payload)
}
function createUSDOrder(storeId, amount) {
// Validate USD amount
if (amount < 0.10) {
throw new Error('Minimum USD amount is $0.10');
}
if (amount > 10000.00) {
throw new Error('Maximum USD amount is $10,000');
}
// Round to 2 decimal places
amount = Math.round(amount * 100) / 100;
return createOrder({
store_id: storeId,
amount: amount,
currency: 'USD'
});
}
KHR Orders
func createKHROrder(storeID string, amount int) (*OrderResponse, error) {
// Validate KHR amount (must be integer)
if amount < 400 {
return nil, errors.New("minimum KHR amount is ៛400")
}
if amount > 40000000 {
return nil, errors.New("maximum KHR amount is ៛40,000,000")
}
payload := CreateOrderRequest{
StoreID: storeID,
Amount: float64(amount), // Convert to float for API
Currency: "KHR",
}
return createOrder(payload)
}
function createKHROrder(storeId, amount) {
// Validate KHR amount (must be integer)
amount = Math.floor(amount); // Ensure no decimals
if (amount < 400) {
throw new Error('Minimum KHR amount is ៛400');
}
if (amount > 40000000) {
throw new Error('Maximum KHR amount is ៛40,000,000');
}
return createOrder({
store_id: storeId,
amount: amount,
currency: 'KHR'
});
}
Currency Conversion
Automatic Conversion
DVPay handles currency conversion automatically at checkout:
- Customer pays in their preferred currency
- Exchange rate applied at transaction time
- Settlement in your merchant account currency
Example Flow:
graph LR
A[Create Order: $100 USD] --> B[Customer Selects KHR]
B --> C[Convert: ≈400,000 KHR]
C --> D[Customer Pays KHR]
D --> E[Merchant Receives USD]
Exchange Rates
DVPay uses real-time exchange rates updated every 15 minutes:
{
"order_id": "ord_abc123",
"requested_currency": "USD",
"requested_amount": 100.00,
"paid_currency": "KHR",
"paid_amount": 405000,
"exchange_rate": 4050.00,
"rate_timestamp": 1696435200
}
Manual Conversion
Convert amounts before creating orders:
// Get current exchange rate
func getExchangeRate() (float64, error) {
resp, err := http.Get("https://merchant.dv.vai247.pro/api/v1/exchange-rate/USD-KHR")
if err != nil {
return 0, err
}
defer resp.Body.Close()
var result struct {
Rate float64 `json:"rate"`
Timestamp int64 `json:"timestamp"`
}
json.NewDecoder(resp.Body).Decode(&result)
return result.Rate, nil
}
// Convert USD to KHR
func convertUSDtoKHR(usdAmount float64) (int, error) {
rate, err := getExchangeRate()
if err != nil {
return 0, err
}
khrAmount := int(math.Round(usdAmount * rate))
return khrAmount, nil
}
// Usage
usdAmount := 25.50
khrAmount, _ := convertUSDtoKHR(usdAmount) // ≈103,275 KHR
// Get current exchange rate
async function getExchangeRate() {
const response = await axios.get(
'https://merchant.dv.vai247.pro/api/v1/exchange-rate/USD-KHR'
);
return response.data.rate;
}
// Convert USD to KHR
async function convertUSDtoKHR(usdAmount) {
const rate = await getExchangeRate();
return Math.round(usdAmount * rate);
}
// Usage
const usdAmount = 25.50;
const khrAmount = await convertUSDtoKHR(usdAmount); // ≈103,275 KHR
Formatting Amounts
Display Formatting
Format amounts correctly for user display:
import (
"fmt"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
func formatCurrency(amount float64, currency string) string {
p := message.NewPrinter(language.English)
switch currency {
case "USD":
return p.Sprintf("$%.2f", amount)
case "KHR":
return p.Sprintf("៛%d", int(amount))
default:
return fmt.Sprintf("%.2f %s", amount, currency)
}
}
// Usage
fmt.Println(formatCurrency(100.50, "USD")) // $100.50
fmt.Println(formatCurrency(405000, "KHR")) // ៛405,000
function formatCurrency(amount, currency) {
const formatters = {
USD: new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}),
KHR: new Intl.NumberFormat('km-KH', {
style: 'currency',
currency: 'KHR',
minimumFractionDigits: 0,
maximumFractionDigits: 0
})
};
return formatters[currency].format(amount);
}
// Usage
console.log(formatCurrency(100.50, 'USD')); // $100.50
console.log(formatCurrency(405000, 'KHR')); // ៛405,000
API Request Formatting
Always send amounts as numbers (not strings):
✅ Correct:
{
"amount": 100.00,
"currency": "USD"
}
❌ Wrong:
{
"amount": "$100.00",
"currency": "USD"
}
Handling Refunds
Refunds are processed in the original payment currency:
{
"order_id": "ord_abc123",
"original_amount": 100.00,
"original_currency": "USD",
"paid_amount": 405000,
"paid_currency": "KHR",
"refund_amount": 50.00,
"refund_currency": "USD" // Must match original_currency
}
func refundOrder(orderID string, refundAmount float64) error {
// Get original order details
order, err := getOrderDetails(orderID)
if err != nil {
return err
}
// Refund must be in original currency
payload := RefundRequest{
OrderID: orderID,
Amount: refundAmount,
Currency: order.Currency, // Use original currency
Reason: "Customer requested refund",
}
return processRefund(payload)
}
async function refundOrder(orderId, refundAmount) {
// Get original order details
const order = await getOrderDetails(orderId);
// Refund must be in original currency
return processRefund({
order_id: orderId,
amount: refundAmount,
currency: order.currency, // Use original currency
reason: 'Customer requested refund'
});
}
Multi-Currency Best Practices
1. Store Currency with Orders
Always store the currency alongside the amount:
const order = {
id: 'ord_123',
amount: 100.00,
currency: 'USD', // Always store currency
created_at: Date.now()
};
2. Validate Currency on Input
function validateCurrency(currency) {
const validCurrencies = ['USD', 'KHR'];
if (!validCurrencies.includes(currency)) {
throw new Error(`Invalid currency: ${currency}`);
}
return true;
}
3. Handle Precision Correctly
function roundAmount(amount, currency) {
switch (currency) {
case 'USD':
return Math.round(amount * 100) / 100; // 2 decimals
case 'KHR':
return Math.floor(amount); // No decimals
default:
return amount;
}
}
4. Display Exchange Rates Transparently
Show customers the conversion rate before payment:
// Display conversion preview
const usdAmount = 25.00;
const rate = await getExchangeRate();
const khrAmount = Math.round(usdAmount * rate);
console.log(`$${usdAmount} USD = ៛${khrAmount.toLocaleString()} KHR`);
console.log(`Exchange rate: 1 USD = ${rate} KHR`);
Currency Conversion Fees
DVPay applies a small conversion fee for cross-currency transactions:
| Transaction Type | Fee |
|---|---|
| Same currency (USD → USD) | 0% |
| Same currency (KHR → KHR) | 0% |
| Cross-currency (USD ↔ KHR) | 1.5% |
Example:
// Customer pays in different currency
const orderAmount = 100.00; // USD
const conversionFee = orderAmount * 0.015; // 1.5%
const totalCharged = orderAmount + conversionFee; // $101.50
Handling Edge Cases
Rounding Differences
Small rounding differences may occur in conversions:
// Expected
const expected = 100.00; // USD
// Actual after conversion
const actual = 100.01; // Due to rounding
// Handle small differences
const tolerance = 0.02;
const isValid = Math.abs(expected - actual) <= tolerance;
Failed Conversions
Handle conversion failures gracefully:
async function createOrderWithFallback(storeId, amount, preferredCurrency) {
try {
return await createOrder({
store_id: storeId,
amount: amount,
currency: preferredCurrency
});
} catch (error) {
if (error.code === 'currency_not_available') {
// Fallback to USD
console.log('Falling back to USD');
return await createOrder({
store_id: storeId,
amount: amount,
currency: 'USD'
});
}
throw error;
}
}
Testing Currency Conversions
Use sandbox environment to test conversions:
# Test USD order
curl -X POST https://sandbox-merchant.dv.vai247.pro/api/v1/payment-gateway/order/create \
-d '{"store_id":"test_store","amount":100.00,"currency":"USD"}'
# Test KHR order
curl -X POST https://sandbox-merchant.dv.vai247.pro/api/v1/payment-gateway/order/create \
-d '{"store_id":"test_store","amount":405000,"currency":"KHR"}'