Overview
Sistecrédito is a Colombian credit financing platform that provides instant credit approval and flexible payment options. This integration uses the Credinet Pay API.
Sistecrédito offers quick credit decisions, making it ideal for customers who prefer financing options.
Prerequisites
Required Credentials
Sistecrédito merchant account
API Key (Ocp-Apim-Subscription-Key)
Application Key
Application Token
Webhook URL configured
Configuration
Environment Variables
Add these to your Firebase Functions configuration:
SC_API_KEY = your_subscription_key
SC_APP_KEY = your_application_key
SC_APP_TOKEN = your_application_token
Code Configuration
From ~/workspace/source/functions/sistecredito.js:6-17:
const IS_SC_SANDBOX = false ; // Set to true for testing
const SC_API_KEY = process . env . SC_API_KEY ;
const SC_APP_KEY = process . env . SC_APP_KEY ;
const SC_APP_TOKEN = process . env . SC_APP_TOKEN ;
const SC_BASE_URL = "https://api.credinet.co/pay" ;
const SC_ORIGEN = IS_SC_SANDBOX ? "Staging" : "Production" ;
const SC_WEBHOOK_URL = "https://sistecreditowebhook-muiondpggq-uc.a.run.app" ;
Creating Sistecrédito Checkout
Function: createSistecreditoCheckout
Creates a Sistecrédito payment request.
Parameters
Firebase authentication token
Array of cart items with product IDs from Firestore
Customer information Colombian ID number (Cédula)
City name (without special characters)
Additional order information
Example Request
const createSistecreditoCheckout = firebase . functions (). httpsCallable ( 'createSistecreditoCheckout' );
const result = await createSistecreditoCheckout ({
userToken: await firebase . auth (). currentUser . getIdToken (),
items: [
{
id: 'prod_456' ,
quantity: 1 ,
color: 'Azul' ,
capacity: '512GB'
}
],
shippingCost: 25000 ,
buyerInfo: {
name: 'Carlos Rodríguez' ,
document: '9876543210' ,
phone: '3109876543' ,
address: 'Avenida 68 #123-45' ,
city: 'Bogotá' ,
department: 'Cundinamarca'
},
extraData: {
needsInvoice: true
}
});
console . log ( result . data );
// { initPoint: 'https://api.credinet.co/pay/...' }
Response
Sistecrédito payment URL to redirect the customer
Implementation Flow
From ~/workspace/source/functions/sistecredito.js:22-167:
1. Authenticate User
const userToken = data . userToken ;
let uid , email ;
if ( userToken ) {
const decoded = await auth . verifyIdToken ( userToken );
uid = decoded . uid ;
email = decoded . email ;
} else if ( context . auth ) {
uid = context . auth . uid ;
email = context . auth . token . email ;
} else {
throw new Error ( "User auth failed" );
}
2. Process Items and Calculate Total
let dbItems = [];
let subtotal = 0 ;
const removeAccents = ( str ) =>
str ? str . normalize ( "NFD" ). replace ( / [ \u0300 - \u036f ] / g , "" ) : "" ;
for ( const item of rawItems ) {
const pDoc = await db . collection ( 'products' ). doc ( item . id ). get ();
if ( ! pDoc . exists ) continue ;
const pData = pDoc . data ();
const price = Number ( pData . price ) || 0 ;
const qty = parseInt ( item . quantity ) || 1 ;
subtotal += price * qty ;
dbItems . push ({
id: item . id ,
name: pData . name ,
price: price ,
quantity: qty ,
color: item . color || "" ,
capacity: item . capacity || "" ,
mainImage: pData . mainImage || pData . image || "https://pixeltechcol.com/img/logo.png"
});
}
const totalAmount = subtotal + shippingCost ;
3. Create Order in Firestore
const newOrderRef = db . collection ( 'orders' ). doc ();
const firebaseOrderId = newOrderRef . id ;
await newOrderRef . set ({
source: 'TIENDA_WEB' ,
createdAt: admin . firestore . FieldValue . serverTimestamp (),
userId: uid ,
userEmail: email ,
userName: clientName ,
phone: clientPhone ,
clientDoc: clientDoc ,
shippingData: shippingData ,
items: dbItems ,
subtotal: subtotal ,
shippingCost: shippingCost ,
total: totalAmount ,
status: 'PENDIENTE_PAGO' ,
paymentMethod: 'SISTECREDITO' ,
paymentStatus: 'PENDING' ,
isStockDeducted: false
});
4. Prepare Sistecrédito Payload
Critical data cleaning for Sistecrédito API:
// Clean document
const cleanDoc = String ( clientDoc ). replace ( / \D / g , '' );
// Parse name
const fullNameParts = String ( clientName ). trim (). split ( " " );
const firstName = fullNameParts [ 0 ];
const lastName = fullNameParts . slice ( 1 ). join ( " " ) || "Apellido" ;
// Format phone
let rawPhone = String ( clientPhone ). replace ( / \D / g , '' );
let cellNumber = rawPhone . startsWith ( '57' ) ? rawPhone . substring ( 2 ) : rawPhone ;
if ( ! cellNumber ) cellNumber = "3000000000" ;
// Clean city - CRITICAL FOR SISTECREDITO
let cleanCity = removeAccents ( shippingData . city || "Bogota" ). trim ();
if ( cleanCity . toLowerCase (). includes ( "bogota" )) {
cleanCity = "Bogota" ; // Must be "Bogota" without "D.C."
}
// Remove any special characters
cleanCity = cleanCity . replace ( / [ ^ a-zA-Z0-9\s ] / g , '' ). trim ();
Important: Sistecrédito is strict about city names. Use “Bogota” not “Bogotá” or “Bogota D.C.”
5. Create Sistecrédito Payment
From ~/workspace/source/functions/sistecredito.js:113-166:
const payload = {
invoice: firebaseOrderId ,
description: `Compra en PixelTech - Orden ${ firebaseOrderId . slice ( 0 , 8 ) } ` ,
paymentMethod: {
paymentMethodId: 2 , // Credit financing
bankCode: 1 ,
userType: 0
},
currency: "COP" ,
value: Math . round ( totalAmount ),
tax: 0 ,
taxBase: 0 ,
sandbox: {
isActive: IS_SC_SANDBOX ,
status: "Approved"
},
urlResponse: `https://pixeltechcol.com/shop/success.html?order= ${ firebaseOrderId } ` ,
urlConfirmation: SC_WEBHOOK_URL ,
methodConfirmation: "POST" ,
client: {
docType: "CC" ,
document: cleanDoc || "11111111" ,
name: removeAccents ( firstName ). substring ( 0 , 50 ),
lastName: removeAccents ( lastName ). substring ( 0 , 50 ),
email: String ( email ). trim (). toLowerCase (),
indCountry: "57" ,
phone: cellNumber ,
country: "CO" ,
city: cleanCity . substring ( 0 , 50 ),
address: removeAccents ( String ( shippingData . address || "Direccion" )). substring ( 0 , 100 ),
ipAddress: "192.168.1.1"
}
};
try {
const response = await axios . post (
` ${ SC_BASE_URL } /create` ,
payload ,
{
headers: {
'SCLocation' : '0,0' ,
'SCOrigen' : SC_ORIGEN ,
'country' : 'CO' ,
'Ocp-Apim-Subscription-Key' : SC_API_KEY ,
'ApplicationKey' : SC_APP_KEY ,
'ApplicationToken' : SC_APP_TOKEN ,
'Content-Type' : 'application/json'
}
}
);
const redirectUrl = response . data ?. data ?. paymentMethodResponse ?. paymentRedirectUrl ;
if ( ! redirectUrl ) {
throw new Error ( "Sistecrédito no devolvió URL de pago." );
}
return { initPoint: redirectUrl };
} catch ( error ) {
console . error ( "❌ Error Sistecrédito:" , error . response ?. data || error . message );
throw new functions . https . HttpsError ( 'internal' , "Error iniciando pago con Sistecrédito." );
}
Required headers for all API requests:
GPS coordinates (use “0,0” if unavailable)
Environment: “Staging” or “Production”
Ocp-Apim-Subscription-Key
API subscription key
Webhook Handling
Function: webhook
Processes Sistecrédito payment notifications.
From ~/workspace/source/functions/sistecredito.js:172-289:
Webhook Payload
Sistecrédito sends POST requests with:
{
"data" : {
"_id" : "transaction_id" ,
"invoice" : "firebase_order_id" ,
"transactionStatus" : "Approved" | "Rejected" | "Cancelled" | "Failed" ,
"value" : 150000
}
}
Processing Approved Payments
if ( status === 'Approved' ) {
await db . runTransaction ( async ( t ) => {
const docSnap = await t . get ( orderRef );
if ( ! docSnap . exists ) return ;
const oData = docSnap . data ();
// Prevent duplicate processing
if ( oData . paymentStatus === 'PAID' || oData . status === 'PAGADO' ) {
console . log ( `⚠️ Webhook duplicado ignorado. Orden ${ orderId } ya pagada.` );
return ;
}
// 1. Deduct stock (same logic as other gateways)
const prodReads = [];
if ( ! oData . isStockDeducted ) {
for ( const item of oData . items ) {
const pRef = db . collection ( 'products' ). doc ( item . id );
const pDoc = await t . get ( pRef );
if ( pDoc . exists ) {
const pData = pDoc . data ();
let newStock = ( pData . stock || 0 ) - ( item . quantity || 1 );
let combinations = pData . combinations || [];
// Handle variants
if ( item . color || item . capacity ) {
const idx = combinations . findIndex ( c =>
( c . color === item . color || ( ! c . color && ! item . color )) &&
( c . capacity === item . capacity || ( ! c . capacity && ! item . capacity ))
);
if ( idx >= 0 ) {
combinations [ idx ]. stock = Math . max ( 0 , combinations [ idx ]. stock - item . quantity );
}
}
prodReads . push ({
ref: pRef ,
stock: Math . max ( 0 , newStock ),
combos: combinations
});
}
}
}
// 2. Update treasury
const accQ = await t . get (
db . collection ( 'accounts' )
. where ( 'gatewayLink' , '==' , 'SISTECREDITO' )
. limit ( 1 )
);
let accDoc = ( ! accQ . empty ) ? accQ . docs [ 0 ] : null ;
if ( ! accDoc ) {
const defQ = await t . get (
db . collection ( 'accounts' )
. where ( 'isDefaultOnline' , '==' , true )
. limit ( 1 )
);
if ( ! defQ . empty ) accDoc = defQ . docs [ 0 ];
}
if ( accDoc ) {
t . update ( accDoc . ref , {
balance: ( Number ( accDoc . data (). balance ) || 0 ) + Number ( oData . total )
});
const incRef = db . collection ( 'expenses' ). doc ();
t . set ( incRef , {
amount: Number ( oData . total ),
category: "Ingreso Ventas Online" ,
description: `Venta Sistecrédito # ${ orderId . slice ( 0 , 8 ) } ` ,
paymentMethod: accDoc . data (). name ,
date: admin . firestore . FieldValue . serverTimestamp (),
type: 'INCOME' ,
orderId: orderId ,
supplierName: oData . userName
});
}
// 3. Apply stock updates
for ( const p of prodReads ) {
t . update ( p . ref , { stock: p . stock , combinations: p . combos });
}
// 4. Create remission (if doesn't exist)
const remRef = db . collection ( 'remissions' ). doc ( orderId );
const remSnap = await t . get ( remRef );
if ( ! remSnap . exists ) {
t . set ( remRef , {
orderId ,
source: 'WEBHOOK_SISTECREDITO' ,
items: oData . items ,
clientName: oData . userName ,
clientPhone: oData . phone ,
clientDoc: oData . clientDoc ,
clientAddress: ` ${ oData . shippingData ?. address } , ${ oData . shippingData ?. city } ` ,
total: oData . total ,
status: 'PENDIENTE_ALISTAMIENTO' ,
type: 'VENTA_WEB' ,
createdAt: admin . firestore . FieldValue . serverTimestamp ()
});
}
// 5. Update order
t . update ( orderRef , {
status: 'PAGADO' ,
paymentStatus: 'PAID' ,
paymentId: txData . _id || 'SISTECREDITO' ,
updatedAt: admin . firestore . FieldValue . serverTimestamp (),
isStockDeducted: true
});
});
}
Handling Failed Payments
else if ( status === 'Rejected' || status === 'Cancelled' || status === 'Failed' ) {
const docCheck = await orderRef . get ();
if ( docCheck . exists && docCheck . data (). paymentStatus !== 'PAID' ) {
await orderRef . update ({
status: 'RECHAZADO' ,
statusDetail: status
});
console . log ( `❌ Orden ${ orderId } Rechazada/Cancelada por Sistecrédito` );
}
}
Sistecrédito Status Flow
Transaction Status Codes
Status Description Action ApprovedPayment approved and processed Update to PAGADO, deduct stock PendingPayment in process Keep as PENDIENTE_PAGO RejectedPayment rejected Mark as RECHAZADO CancelledPayment cancelled by user Mark as RECHAZADO FailedTechnical failure Mark as RECHAZADO
Payment Methods
Sistecrédito supports multiple payment methods via paymentMethodId:
ID Method Description 1PSE Electronic bank transfer 2Credit Sistecrédito financing 3Cash Cash payment locations
PixelTech uses paymentMethodId: 2 for credit financing.
Data Requirements
City Name Handling
Sistecrédito is very strict about city names. Must not contain:
Accents (á, é, í, ó, ú)
Special characters
Extra descriptors (like “D.C.”)
// Clean city implementation
let cleanCity = removeAccents ( shippingData . city || "Bogota" ). trim ();
if ( cleanCity . toLowerCase (). includes ( "bogota" )) {
cleanCity = "Bogota" ; // NOT "Bogotá" or "Bogota D.C."
}
cleanCity = cleanCity . replace ( / [ ^ a-zA-Z0-9\s ] / g , '' ). trim ();
Document Validation
const cleanDoc = String ( clientDoc ). replace ( / \D / g , '' );
// Result: "1234567890" (numbers only)
Address Length Limits
Name : 50 characters max
Address : 100 characters max
City : 50 characters max
Testing
Sandbox Mode
Enable sandbox in configuration:
const IS_SC_SANDBOX = true ;
const SC_ORIGEN = "Staging" ;
// In payload
sandbox : {
isActive : true ,
status : "Approved" // Force approval in sandbox
}
Test Flow
Set IS_SC_SANDBOX = true
Create checkout with valid test data
Payment will be auto-approved in sandbox
Webhook will be triggered immediately
Verify order status changes to PAGADO
Check stock deduction
Confirm treasury update
Test Customer Data
{
name : "Test User" ,
document : "1234567890" ,
phone : "3001234567" ,
email : "[email protected] " ,
address : "Calle 123" ,
city : "Bogota" // No accents!
}
Treasury Configuration
Create a Sistecrédito treasury account:
// Firestore: accounts collection
{
name : "Sistecrédito" ,
gatewayLink : "SISTECREDITO" ,
balance : 0 ,
isDefaultOnline : false ,
type : "ONLINE_PAYMENT"
}
Error Handling
Error iniciando pago con Sistecrédito
Cause: API error or invalid payloadSolution: Check logs for detailed error from Sistecrédito APICommon issues:
City name contains special characters
Invalid phone number format
Missing required fields
Sistecrédito no devolvió URL de pago
Cause: Invalid response from APISolution: Verify credentials and payload structure
City name validation error
Cause: City contains accents or special charactersSolution: Use removeAccents() and strip special characterscleanCity = removeAccents ( city ). replace ( / [ ^ a-zA-Z0-9\s ] / g , '' ). trim ();
Duplicate webhook processing
Cause: Sistecrédito sent multiple notificationsSolution: Already handled - system checks paymentStatus === 'PAID'
Best Practices
Clean all data - Remove accents and special characters
Validate city names - Use simple names without descriptors
Test in sandbox - Always test before production
Monitor webhooks - Track all status changes
Handle retries - Implement idempotent webhook processing
Common Integration Issues
Issue: Invalid city name
Problem: API rejects city names with accents or special characters
Solution:
let cleanCity = removeAccents ( city ). trim ();
if ( cleanCity . toLowerCase (). includes ( "bogota" )) cleanCity = "Bogota" ;
cleanCity = cleanCity . replace ( / [ ^ a-zA-Z0-9\s ] / g , '' );
Problem: API expects 10-digit Colombian mobile
Solution:
let phone = String ( phoneInput ). replace ( / \D / g , '' );
if ( phone . startsWith ( '57' )) phone = phone . substring ( 2 );
if ( phone . length !== 10 ) phone = "3000000000" ; // Fallback
Monitoring
Key Metrics
Payment creation success rate
Webhook processing time
Approval vs rejection rate
Average transaction value
Logging
console . log ( `🚀 Iniciando Checkout Sistecrédito ( ${ SC_ORIGEN } )...` );
console . log ( "✅ Sistecrédito Approved Procesado Correctamente" );
console . log ( `❌ Orden ${ orderId } Rechazada/Cancelada por Sistecrédito` );
Next Steps
MercadoPago Add credit card payments
ADDI Add another financing option
Treasury Setup Configure Sistecrédito account
Order Management Process Sistecrédito orders