Skip to main content

Overview

Transactions in Mueve represent the core exchange operations where users buy or sell USD, converting to or from Bolivares (Bs) at the current market rate. Each transaction includes comprehensive validation, commission calculation, and status tracking to ensure secure and accurate currency exchanges.

Transaction Types

Mueve supports two primary transaction types:
{
  "transaction_type": "Comprar",
  "amount_usd": 50.00,
  "rate_bs": 42.15,
  "payment_reference": "REF123456789",
  "type_pay": "bank_transfer",
  "recipient_account": "0102-1234-5678"
}

Buy vs Sell Logic

The calculation differs significantly between buying and selling:
When buying USD, the commission is added to the amount:
const total_usd = amount_usd + commission_usd;
const total_bs = total_usd * rate_bs;
Example: Buy $50 USD
  • Base amount: $50.00
  • Commission: $1.40
  • Total to pay: $51.40 USD (2,166.51 Bs at rate 42.15)
When selling USD, the commission is deducted from the amount:
const net_usd = amount_usd - commission_usd;
const total_bs = net_usd * rate_bs;
Example: Sell $50 USD
  • Base amount: $50.00
  • Commission: $1.40
  • Net received: $48.60 USD (2,048.49 Bs at rate 42.15)
// Default: treat as Comprar
const total_usd = round2(A + commission_usd);
return {
  commission_usd,
  total_usd,
  total_bs: round2(total_usd * r),
  raw_amount_usd: A,
};
For sell transactions, if the commission equals or exceeds the amount, the transaction is rejected with error: “La comisión excede o iguala el monto proporcionado”

Transaction Status

All transactions are created with an initial status that tracks their lifecycle:

Status: Pendiente (Pending)

Every new transaction starts with the “Pendiente” status:
const [result] = await pool.query(
  `INSERT INTO transactions 
  (user_id, transaction_type, amount_usd, commission_usd, total_usd, rate_bs, total_bs, payment_reference, status, type_pay, recipient_account)
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'Pendiente', ?, ?)`,
  [
    userId,
    transaction_type,
    amount_usd,
    commission_usd,
    total_usd,
    rate_bs,
    total_bs,
    payment_reference,
    type_pay,
    recipient_account,
  ],
);
The “Pendiente” status indicates that the transaction has been recorded but is awaiting verification or completion by the system administrator.

Payment Reference Validation

To prevent duplicate transactions and fraud, Mueve validates that each payment reference is unique:
1

Check Reference Uniqueness

Before creating a transaction, the system queries the database to verify the payment reference hasn’t been used before
2

Reject Duplicates

If the reference exists, the transaction is rejected with a 409 Conflict status
3

Proceed with Creation

Only unique references are allowed to proceed to transaction creation
const isPaymentReferenceUsed = async (reference) => {
  if (!reference) return false;
  const [rows] = await pool.query(
    "SELECT id FROM transactions WHERE payment_reference = ? LIMIT 1",
    [reference],
  );
  return rows.length > 0;
};
Always generate unique payment references for each transaction. Using the same reference twice will result in a 409 Conflict error.

Rate Validation

Mueve implements strict rate validation to ensure users complete transactions with current market rates:

Validation Process

1

Fetch Current Rate

The system retrieves the current USD to Bolivares rate from Redis
2

Compare with User Rate

The rate submitted by the user is compared against the current rate
3

Apply Tolerance

A tolerance of ±0.35 is applied to account for minor fluctuations
4

Reject or Proceed

If the difference exceeds tolerance, the transaction is rejected
const rateInRedis = await getBTC();
const rateSentByUser = parseFloat(rate_bs);

const tolerance = 0.35;
const diff = Math.abs(rateInRedis - rateSentByUser);

if (diff > tolerance) {
  return res.status(400).json({
    message:
      "La tasa ha cambiado. Por favor actualiza la página para continuar.",
  });
}
To avoid rate validation errors, always fetch the latest rate immediately before creating a transaction. Rates cached for more than a few seconds may become stale.

Required Fields

All transaction creation requests must include the following fields:
transaction_type
string
required
Type of transaction: "Comprar" (buy) or "Vender" (sell)
amount_usd
number
required
Amount in USD (base amount before commission)
rate_bs
number
required
Current USD to Bolivares exchange rate (must be within tolerance)
payment_reference
string
required
Unique payment reference identifier
type_pay
string
Payment method (e.g., "bank_transfer", "mobile_payment")
recipient_account
string
Destination account identifier
if (!transaction_type || !amount_usd || !rate_bs || !payment_reference) {
  return res
    .status(400)
    .json({ message: "Todos los campos son obligatorios" });
}

Transaction Totals Calculation

The system uses a computeTotals helper function that handles all financial calculations:
const computeTotals = ({ transaction_type, amount_usd, rate_bs }) => {
  const commission_usd = round2(getCommissionUsd(amount_usd));
  const A = parseFloat(amount_usd);
  const r = parseFloat(rate_bs);

  if (isNaN(A) || isNaN(r)) {
    throw new Error("Invalid numeric values for amount or rate");
  }

  if (transaction_type === "Vender" || transaction_type === "VENTA") {
    const net_usd = round2(A - commission_usd);
    if (net_usd <= 0) {
      return { error: "La comisión excede o iguala el monto proporcionado" };
    }
    return {
      commission_usd,
      total_usd: net_usd,
      total_bs: round2(net_usd * r),
      raw_amount_usd: A,
    };
  }

  // Default: treat as Comprar
  const total_usd = round2(A + commission_usd);
  return {
    commission_usd,
    total_usd,
    total_bs: round2(total_usd * r),
    raw_amount_usd: A,
  };
};
All monetary values are rounded to 2 decimal places using the round2 helper function to ensure consistency across calculations.

Retrieving Transactions

Users can retrieve their transaction history through authenticated endpoints:

Get All User Transactions

export const getUserTransactions = async (req, res) => {
  try {
    const userId = req.user.id;
    const [transactions] = await pool.query(
      "SELECT * FROM transactions WHERE user_id = ? ORDER BY created_at DESC",
      [userId],
    );
    res.json(transactions);
  } catch (error) {
    console.error("Error al obtener transacciones:", error);
    res.status(500).json({ message: "Error del servidor" });
  }
};

Get Specific Transaction

export const getTransactionById = async (req, res) => {
  try {
    const userId = req.user.id;
    const transactionId = req.params.id;

    const [rows] = await pool.query(
      "SELECT * FROM transactions WHERE id = ? AND user_id = ?",
      [transactionId, userId],
    );

    if (rows.length === 0) {
      return res.status(404).json({ message: "Transacción no encontrada" });
    }

    res.json(rows[0]);
  } catch (error) {
    console.error("Error al obtener transacción:", error);
    res.status(500).json({ message: "Error del servidor" });
  }
};
Transactions are scoped to the authenticated user - users can only view their own transactions.

Error Handling

The transaction system implements comprehensive error handling:
Error CodeScenarioMessage
400Missing required fields”Todos los campos son obligatorios”
400Rate changed beyond tolerance”La tasa ha cambiado. Por favor actualiza la página para continuar.”
400Commission exceeds sell amount”La comisión excede o iguala el monto proporcionado”
409Duplicate payment reference”La referencia de pago ya fue utilizada”
404Transaction not found”Transacción no encontrada”
500Server error”Error del servidor”

Best Practices

  1. Always validate rates - Fetch the latest rate immediately before transaction creation
  2. Generate unique references - Use UUIDs or timestamp-based identifiers for payment references
  3. Handle rate change errors - Implement automatic retry with updated rates when validation fails
  4. Show commission clearly - Display both base amount and total (with commission) to users
  5. Verify minimum amounts - Ensure amounts are sufficient to cover commissions, especially for sell transactions

Build docs developers (and LLMs) love