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 API access token (production or sandbox)
Client-side public key for payment forms
MERCADOPAGO_WEBHOOK_SECRET
Secret for validating webhook signatures
Payment Flow
Select Credit Pack
User chooses pack from pricing page
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
}
});
Redirect to MercadoPago
window . location . href = data . init_point ;
Complete Payment
User pays via:
Credit/debit card
Bank transfer
Cash (Rapipago, Pago Fácil)
Digital wallets
Return to Platform
MercadoPago redirects to success/failure URL
Webhook Notification
Platform receives payment confirmation
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.created
payment.updated
payment.refunded
Payment initiated, not yet processed
Payment status changed (approved, rejected, pending)
Payment refunded (handle credit reversal)
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
Insufficient Funds
Invalid Card
Security Code
Expired Card
if ( payment . status === 'rejected' && payment . status_detail === 'cc_rejected_insufficient_amount' ) {
return 'Fondos insuficientes en la tarjeta' ;
}
if ( payment . status_detail === 'cc_rejected_bad_filled_card_number' ) {
return 'Número de tarjeta inválido' ;
}
if ( payment . status_detail === 'cc_rejected_bad_filled_security_code' ) {
return 'Código de seguridad incorrecto' ;
}
if ( payment . status_detail === 'cc_rejected_bad_filled_date' ) {
return 'Fecha de vencimiento inválida' ;
}
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 });