Overview
PixelTech integrates multiple payment methods through Firebase Cloud Functions. Each gateway creates orders in Firestore with status PENDIENTE_PAGO, processes payment confirmations via webhooks, and updates inventory automatically.
MercadoPago Integration
createMercadoPagoPreference
Creates a payment preference and returns a checkout URL.
Location : functions/mercadopago.js:18-165
Function Signature :
exports . createPreference = async ( data , context )
Request payload from client Array of cart items with id, quantity, color, capacity
Shipping cost in COP (default: 0)
Customer information: name, email, phone, address
Additional data: userName, clientDoc, shippingData, needsInvoice
Firebase ID token for authentication
MercadoPago preference ID
Checkout URL to redirect user
Implementation Details :
Authentication : Verifies user token using Firebase Auth
Price Validation : Fetches real prices from Firestore products collection
Order Creation : Creates order with status PENDIENTE_PAGO in Firestore
Expiration : Sets 30-minute expiration on payment link
Webhook Registration : Configures notification URL for payment confirmations
Example Usage :
const result = await createMercadoPagoPreference ({
items: [
{ id: 'prod123' , quantity: 2 , color: 'negro' , capacity: '128GB' }
],
shippingCost: 15000 ,
buyerInfo: {
name: 'Juan Pérez' ,
email: '[email protected] ' ,
phone: '3001234567' ,
address: 'Calle 123 #45-67'
},
userToken: 'eyJhbGc...' ,
extraData: { clientDoc: '1234567890' }
});
// Result: { preferenceId: 'MP-123456', initPoint: 'https://...' }
mercadoPagoWebhook
Processes payment notifications from MercadoPago.
Location : functions/mercadopago.js:174-326
Endpoint : POST /mercadoPagoWebhook
Webhook Flow :
Receives notification with paymentId from MercadoPago
Fetches payment status from MercadoPago API
If status === 'approved':
Updates order status to PAGADO
Deducts inventory from products collection
Credits payment amount to treasury account
Creates income record in expenses collection
Generates remission document
If status === 'rejected':
Updates order status to RECHAZADO
Code Example (from mercadopago.js:204-305):
if ( status === 'approved' ) {
await db . runTransaction ( async ( t ) => {
// 1. Deduct inventory
for ( const i of oData . items ) {
const pRef = db . collection ( 'products' ). doc ( i . id );
const pDoc = await t . get ( pRef );
let newStock = ( pData . stock || 0 ) - ( i . quantity || 1 );
t . update ( pRef , { stock: newStock });
}
// 2. Credit treasury account
const accDoc = await t . get (
db . collection ( 'accounts' ). where ( 'gatewayLink' , '==' , 'MERCADOPAGO' ). limit ( 1 )
);
t . update ( accDoc . ref , {
balance: accDoc . data (). balance + oData . total
});
// 3. Update order
t . update ( orderRef , {
status: 'PAGADO' ,
paymentStatus: 'PAID'
});
});
}
ADDI Integration (Buy Now, Pay Later)
createAddiCheckout
Creates an ADDI financing application.
Location : functions/addi.js:59-252
Function Signature :
exports . createAddiCheckout = async ( data , context )
Accepts identical payload structure as createMercadoPagoPreference
ADDI checkout URL for financing application
Key Differences :
Requires OAuth token from ADDI Auth service
Normalizes customer data (removes accents, formats phone numbers)
Posts to ADDI API endpoint: https://api.addi.com/v1/online-applications
Implementation (from addi.js:163-219):
const addiToken = await getAddiToken ();
const addiPayload = {
orderId: firebaseOrderId ,
totalAmount: totalAmount . toFixed ( 1 ),
currency: "COP" ,
items: dbItems . map ( i => ({
sku: i . id ,
name: removeAccents ( i . name ),
quantity: String ( i . quantity ),
unitPrice: Math . round ( i . price )
})),
client: {
idType: "CC" ,
idNumber: cleanDoc ,
firstName: removeAccents ( firstName ),
email: email . trim (). toLowerCase (),
cellphone: cellNumber
},
allyUrlRedirection: {
callbackUrl: WEBHOOK_URL ,
redirectionUrl: `https://pixeltechcol.com/shop/success.html`
}
};
const response = await axios . post (
` ${ ADDI_BASE_URL } /v1/online-applications` ,
addiPayload
);
addiWebhook
Processes ADDI payment status updates.
Location : functions/addi.js:260-376
Endpoint : POST /addiWebhook
Webhook Payload :
{
"orderId" : "abc123" ,
"status" : "APPROVED" ,
"applicationId" : "addi-app-456"
}
Duplicate Prevention (from addi.js:280-284):
if ( oData . paymentStatus === 'PAID' || oData . status === 'PAGADO' ) {
console . log ( `⚠️ Webhook duplicado ignorado.` );
return ; // Exit transaction without processing
}
Sistecrédito Integration
createSistecreditoCheckout
Creates a Sistecrédito payment link.
Location : functions/sistecredito.js:22-167
Function Signature :
exports . createSistecreditoCheckout = async ( data , context )
Identical to MercadoPago/ADDI
API Request (from sistecredito.js:113-156):
const payload = {
invoice: firebaseOrderId ,
description: `Compra en PixelTech - Orden ${ firebaseOrderId . slice ( 0 , 8 ) } ` ,
paymentMethod: {
paymentMethodId: 2 , // PSE
bankCode: 1 ,
userType: 0
},
currency: "COP" ,
value: Math . round ( totalAmount ),
urlResponse: `https://pixeltechcol.com/shop/success.html` ,
urlConfirmation: SC_WEBHOOK_URL ,
client: {
docType: "CC" ,
document: cleanDoc ,
name: removeAccents ( firstName ),
email: email . trim (). toLowerCase (),
city: cleanCity ,
address: removeAccents ( shippingData . address )
}
};
const response = await axios . post (
` ${ SC_BASE_URL } /create` ,
payload ,
{
headers: {
'Ocp-Apim-Subscription-Key' : SC_API_KEY ,
'ApplicationKey' : SC_APP_KEY
}
}
);
sistecreditoWebhook
Location : functions/sistecredito.js:172-289
Endpoint : POST /sistecreditoWebhook
Status Mapping :
Approved → Order status: PAGADO
Rejected / Cancelled / Failed → Order status: RECHAZADO
Cash on Delivery (COD)
createCODOrder
Creates an order for cash payment on delivery.
Location : functions/cod.js:4-136
Function Signature :
exports . createCODOrder = async ( data , context )
Key Differences from Online Payments :
Immediate Stock Deduction : Inventory is reserved immediately
Status : Order created with status PENDIENTE (not PENDIENTE_PAGO)
No Webhook : Payment confirmed manually by admin
Transaction Logic (from cod.js:45-128):
await db . runTransaction ( async ( t ) => {
const pendingUpdates = [];
// Phase 1: Read and calculate
for ( const item of rawItems ) {
const pDoc = await t . get ( pRef );
let newStock = ( pData . stock || 0 ) - qty ;
if ( newStock < 0 ) throw new Error ( `Sin stock: ${ pData . name } ` );
pendingUpdates . push ({
ref: pRef ,
data: { stock: newStock }
});
}
// Phase 2: Write all changes
for ( const update of pendingUpdates ) {
t . update ( update . ref , update . data );
}
t . set ( newOrderRef , orderData );
t . set ( remissionRef , remissionData );
});
Important : COD orders deduct stock immediately. If customer cancels, admin must manually restore inventory.
Common Payment Flow
All payment methods follow this pattern:
Client Calls Cloud Function
Frontend sends cart items, buyer info, and user token
Function Creates Order
Order saved to Firestore with status PENDIENTE_PAGO
User Redirected to Gateway
Customer completes payment on external platform
Webhook Receives Confirmation
Gateway POSTs payment result to webhook endpoint
Order Finalized
If approved: inventory deducted, order marked PAGADO, remission created
Error Handling
All functions use Firebase HttpsError for consistent error responses:
if ( ! items || ! items . length ) {
throw new functions . https . HttpsError ( 'invalid-argument' , 'Cart is empty' );
}
try {
// Payment logic
} catch ( error ) {
console . error ( "Payment Error:" , error );
throw new functions . https . HttpsError ( 'internal' , error . message );
}
Security Features
Price Validation All prices fetched from Firestore to prevent client-side tampering
User Authentication Firebase ID tokens verified before processing orders
Webhook Verification Payment status confirmed with gateway API (not just webhook payload)
Atomic Transactions Firestore transactions ensure inventory and payment consistency