Essentials

Currencies & Conversion

Handle multi-currency payments with USD and KHR in DVPay

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"
}
Currency codes are case-sensitive. Use uppercase: USD, KHR

Amount Limits

Each currency has minimum and maximum transaction limits:

CurrencyMinimumMaximumDecimal Places
USD$0.10$10,0002
KHR៛400៛40,000,0000
KHR amounts must be whole numbers (no decimals). $1 USD ≈ 4,000 KHR (rates vary).

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)
}

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)
}

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
}
Exchange rates include a small conversion fee (typically 1-2%). Exact rates are shown to customers before payment.

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

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

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)
}
Partial refunds must not exceed the original order amount in the same currency.

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 TypeFee
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
Conversion fees are automatically included in the exchange rate shown to customers.

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"}'

Next Steps

Order Lifecycle

Understand payment order states and transitions

API Reference

Explore detailed API endpoint documentation