Skip to main content
Cabina uses MercadoPago for payment processing, providing secure checkout with multiple payment methods popular in Latin America.

MercadoPago Setup

Edge Function Integration

Payments processed via mercadopago-payment Edge Function:
import { serve } from 'https://deno.land/[email protected]/http/server.ts';
import MercadoPago from 'mercadopago';

serve(async (req) => {
  const { user_id, credits, price, pack_name, redirect_url } = await req.json();
  
  // Initialize MercadoPago
  mercadopago.configure({
    access_token: Deno.env.get('MERCADOPAGO_ACCESS_TOKEN')
  });
  
  // Create payment preference
  const preference = await mercadopago.preferences.create({
    items: [{
      title: `${pack_name} - ${credits} Créditos`,
      unit_price: price,
      quantity: 1
    }],
    back_urls: {
      success: `${redirect_url}?payment=success`,
      failure: `${redirect_url}?payment=failure`,
      pending: `${redirect_url}?payment=pending`
    },
    notification_url: `${redirect_url}/api/mercadopago-webhook`,
    metadata: {
      user_id,
      credits,
      pack_name
    }
  });
  
  return new Response(
    JSON.stringify({ init_point: preference.init_point }),
    { headers: { 'Content-Type': 'application/json' } }
  );
});

Environment Variables

MERCADOPAGO_ACCESS_TOKEN
string
required
MercadoPago API access token (production or sandbox)
MERCADOPAGO_PUBLIC_KEY
string
required
Client-side public key for payment forms
MERCADOPAGO_WEBHOOK_SECRET
string
required
Secret for validating webhook signatures

Payment Flow

1

Select Credit Pack

User chooses pack from pricing page
2

Invoke Edge Function

const { data } = await supabase.functions.invoke('mercadopago-payment', {
  body: {
    user_id: session.user.id,
    credits: pack.credits,
    price: pack.price,
    pack_name: pack.name,
    redirect_url: window.location.origin
  }
});
3

Redirect to MercadoPago

window.location.href = data.init_point;
4

Complete Payment

User pays via:
  • Credit/debit card
  • Bank transfer
  • Cash (Rapipago, Pago Fácil)
  • Digital wallets
5

Return to Platform

MercadoPago redirects to success/failure URL
6

Webhook Notification

Platform receives payment confirmation
7

Credits Added

User balance updated automatically

Webhook Handler

Webhook Edge Function

serve(async (req) => {
  const body = await req.json();
  
  // Validate webhook signature
  const signature = req.headers.get('x-signature');
  if (!validateSignature(signature, body)) {
    return new Response('Invalid signature', { status: 401 });
  }
  
  // Get payment details
  const paymentId = body.data.id;
  const payment = await mercadopago.payment.get(paymentId);
  
  // Only process approved payments
  if (payment.status !== 'approved') {
    return new Response('Payment not approved', { status: 200 });
  }
  
  // Extract metadata
  const { user_id, credits } = payment.metadata;
  
  // Add credits to user
  const { error } = await supabase
    .from('profiles')
    .update({ 
      credits: supabase.raw(`credits + ${credits}`) 
    })
    .eq('id', user_id);
  
  // Log transaction
  await supabase.from('transactions').insert({
    user_id,
    amount: credits,
    type: 'purchase',
    payment_id: paymentId,
    details: {
      method: payment.payment_method_id,
      price: payment.transaction_amount,
      currency: payment.currency_id
    }
  });
  
  return new Response('OK', { status: 200 });
});

Webhook Events

MercadoPago sends notifications for:
Payment initiated, not yet processed

Payment Methods

Supported in Argentina

Credit Cards

Visa, Mastercard, American Express, Cabal, Naranja

Debit Cards

Visa Débito, Maestro, Cabal Débito

Bank Transfer

Direct bank debits (CBU/CVU)

Cash

Rapipago, Pago Fácil, Provincia NET

Digital Wallets

Mercado Pago account balance

Installments

Up to 12 monthly payments (varies by card)

Payment States

Status Handling

const handlePaymentReturn = () => {
  const params = new URLSearchParams(window.location.search);
  const status = params.get('payment');
  
  switch (status) {
    case 'success':
      showToast('✅ Pago aprobado. Créditos agregados.', 'success');
      fetchProfile(); // Refresh credit balance
      break;
    
    case 'pending':
      showToast('⏳ Pago pendiente. Te avisaremos cuando se apruebe.', 'info');
      break;
    
    case 'failure':
      showToast('❌ Pago rechazado. Intenta nuevamente.', 'error');
      break;
  }
};

Pending Payments

Some methods (cash, bank transfer) process asynchronously:
Pending payments show in user’s transaction history. Credits added automatically when MercadoPago confirms payment.

Error Handling

Common Errors

if (payment.status === 'rejected' && payment.status_detail === 'cc_rejected_insufficient_amount') {
  return 'Fondos insuficientes en la tarjeta';
}

Retry Logic

Allow users to retry failed payments:
const handleRetryPayment = async () => {
  // Clear previous error state
  setErrorMessage(null);
  
  // Reinitiate payment flow
  await handlePayment(previousPack);
};

Security

PCI Compliance

Never store credit card details. MercadoPago handles all sensitive payment data.

Webhook Validation

const validateSignature = (signature: string, body: any): boolean => {
  const crypto = require('crypto');
  const secret = Deno.env.get('MERCADOPAGO_WEBHOOK_SECRET');
  
  const hash = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(body))
    .digest('hex');
  
  return hash === signature;
};

Duplicate Payment Prevention

// Check if payment already processed
const { data: existing } = await supabase
  .from('transactions')
  .select('id')
  .eq('payment_id', paymentId)
  .single();

if (existing) {
  return new Response('Already processed', { status: 200 });
}

Testing

Sandbox Mode

Use test credentials for development:
# Sandbox access token
MERCADOPAGO_ACCESS_TOKEN=TEST-xxxxxxxxxxxx

# Sandbox public key  
MERCADOPAGO_PUBLIC_KEY=TEST-xxxxxxxxxxxx

Test Cards

Approved Payment

Card: 5031 7557 3453 0604 CVV: 123 Expiry: 11/25

Rejected Payment

Card: 5031 4332 1540 6351 CVV: 123 Expiry: 11/25

Webhook Testing

Use ngrok for local webhook testing:
ngrok http 54321

# Set webhook URL in MercadoPago
https://your-ngrok-url.ngrok.io/functions/v1/mercadopago-webhook

Transaction Logging

All payments logged for audit:
interface Transaction {
  id: string;
  user_id: string;
  amount: number;           // Credits purchased
  type: 'purchase';
  payment_id: string;       // MercadoPago payment ID
  details: {
    method: string;         // 'visa', 'master', 'rapipago', etc.
    price: number;          // Amount in currency
    currency: string;       // 'ARS', 'USD', etc.
    installments: number;   // Payment plan
  };
  created_at: timestamp;
}
View in admin dashboard:
const { data } = await supabase
  .from('transactions')
  .select('*, profiles(full_name, email)')
  .eq('type', 'purchase')
  .order('created_at', { ascending: false });

Build docs developers (and LLMs) love