Overview
The PSE client enables you to accept payments through Colombia’s PSE (Pagos Seguros en Línea) payment system. PSE allows users to pay directly from their bank accounts.
Features
List available PSE banks
Create PSE top-up orders
Auto-execute with user bank selection
Redirect users to complete payment
List PSE Banks
Retrieve all financial institutions available for PSE payments:
const result = await user . swap . pse . banks ();
for ( const bank of result . banks ) {
console . log ( ` ${ bank . code } : ${ bank . name } ` );
}
Response
Array of available PSE banks Financial institution code used when creating orders Example : "1", "1007", "1051"
Human-readable bank name Example : "Banco de Bogotá", "Bancolombia", "Davivienda"
Create PSE Order
Create a swap order using PSE as the payment source:
import { SDK } from '@bloque/sdk' ;
const bloque = new SDK ({
origin: process . env . ORIGIN ,
auth: { type: 'apiKey' , apiKey: process . env . API_KEY },
mode: 'sandbox' ,
platform: 'node'
});
const user = await bloque . connect ( 'nestor' );
// 1. Find rates
const rates = await user . swap . findRates ({
fromAsset: 'COP/2' ,
toAsset: 'DUSD/6' ,
fromMediums: [ 'pse' ],
toMediums: [ 'kusama' ],
amountSrc: '10000000' // 100,000.00 COP
});
// 2. Get available banks
const banks = await user . swap . pse . banks ();
// 3. Create PSE order with auto-execution
const result = await user . swap . pse . create ({
rateSig: rates . rates [ 0 ]. sig ,
toMedium: 'kusama' ,
amountSrc: '10000000' ,
depositInformation: {
urn: 'did:bloque:account:card:usr-abc123:crd-xyz789'
},
args: {
bankCode: banks . banks [ 0 ]. code ,
userType: 0 ,
customerEmail: '[email protected] ' ,
userLegalIdType: 'CC' ,
userLegalId: '1234567890' ,
customerData: {
fullName: 'John Doe' ,
phoneNumber: '3001234567'
}
}
});
// 4. Redirect user to complete payment
if ( result . execution ?. result . how ?. url ) {
console . log ( 'Redirect user to:' , result . execution . result . how . url );
// In a web app: window.location.href = result.execution.result.how.url;
}
Parameters
Rate signature obtained from findRates const rates = await user . swap . findRates ({ ... });
const rateSig = rates . rates [ 0 ]. sig ;
Destination medium where funds will be deposited Common values : "kusama", "pomelo"
Source amount as scaled bigint string (required if type is 'src') Example : "10000000" = 100,000.00 COP for COP/2
Destination amount as scaled bigint string (required if type is 'dst')
type
'src' | 'dst'
default: "src"
Order type:
src: Specify exact source amount to pay
dst: Specify exact destination amount to receive
depositInformation
DepositInformation
required
Account where funds will be deposited Account URN in the format did:bloque:account:card:usr-{userId}:crd-{cardId} Example : "did:bloque:account:card:usr-abc123:crd-xyz789"
PSE payment arguments for auto-execution Bank code from the PSE banks list Get this from user.swap.pse.banks() Example : "1" (Banco de Bogotá), "1007" (Bancolombia)
User type:
0: Natural person (individual)
1: Legal entity (company)
args.userLegalIdType
'CC' | 'NIT' | 'CE'
required
Legal identification type:
CC: Cédula de Ciudadanía (Citizenship Card)
NIT: Número de Identificación Tributaria (Tax ID)
CE: Cédula de Extranjería (Foreigner ID)
Legal identification number Example : "1234567890"
Additional customer information args.customerData.fullName
Customer’s full name as it appears on their ID Example : "John Doe"
args.customerData.phoneNumber
Customer’s phone number including country code Example : "3001234567"
Optional webhook URL for order status notifications Bloque will POST order updates to this URL
Specific instruction node ID to execute (defaults to first node)
Additional metadata to attach to the order Example : { reference: 'ORDER-123', userId: 'user-456' }
Response
The created swap order Rate signature used for this order
Taker URN (user creating the order)
Maker URN (liquidity provider)
Source asset (e.g., "COP/2")
Destination asset (e.g., "DUSD/6")
Destination medium (e.g., "kusama")
Source amount as scaled bigint string
Destination amount as scaled bigint string
Instruction graph ID for tracking execution
Order status (e.g., "pending", "completed", "failed")
Custom metadata attached to the order
ISO timestamp of creation
ISO timestamp of last update
Execution result from auto-execution (only present if args were provided) Node ID that was executed
Execution result details Execution status (e.g., "paused", "completed")
Description of what the user needs to do
Instructions for completing this step Action type (typically "REDIRECT")
URL to redirect the user to complete the PSE payment This is the PSE checkout URL where the user completes their payment
Callback token for tracking
Request ID for tracking and support
PSE Payment Flow
The complete PSE payment flow:
// 1. User selects PSE payment and amount
const userAmount = '10000000' ; // 100,000.00 COP
// 2. Find available rates
const rates = await user . swap . findRates ({
fromAsset: 'COP/2' ,
toAsset: 'DUSD/6' ,
fromMediums: [ 'pse' ],
toMediums: [ 'kusama' ],
amountSrc: userAmount
});
if ( rates . rates . length === 0 ) {
throw new Error ( 'No rates available' );
}
// 3. Show user the best rate and fees
const bestRate = rates . rates [ 0 ];
console . log ( `You pay: ${ Number ( userAmount ) / 100 } COP` );
console . log ( `You receive: ~ ${ ( Number ( userAmount ) * bestRate . ratio ) / 1000000 } DUSD` );
console . log ( `Fee: ${ bestRate . fee . value } ` );
// 4. User selects their bank
const banks = await user . swap . pse . banks ();
// Display banks to user, let them select
const selectedBank = banks . banks . find ( b => b . code === userSelectedCode );
// 5. User provides their information and confirms
const customerInfo = {
email: '[email protected] ' ,
fullName: 'John Doe' ,
idType: 'CC' ,
idNumber: '1234567890' ,
phoneNumber: '3001234567' ,
userType: 0 // Natural person
};
// 6. Create the PSE order
const result = await user . swap . pse . create ({
rateSig: bestRate . sig ,
toMedium: 'kusama' ,
amountSrc: userAmount ,
depositInformation: {
urn: 'did:bloque:account:card:usr-abc:crd-xyz'
},
args: {
bankCode: selectedBank . code ,
userType: customerInfo . userType ,
customerEmail: customerInfo . email ,
userLegalIdType: customerInfo . idType ,
userLegalId: customerInfo . idNumber ,
customerData: {
fullName: customerInfo . fullName ,
phoneNumber: customerInfo . phoneNumber
}
},
webhookUrl: 'https://yourapp.com/webhooks/pse' ,
metadata: {
userId: 'user-123' ,
sessionId: 'session-456'
}
});
// 7. Redirect user to PSE checkout
if ( result . execution ?. result . how ?. url ) {
// In browser:
window . location . href = result . execution . result . how . url ;
// Or display as QR code, send via SMS, etc.
}
// 8. User completes payment on PSE site
// PSE redirects back to your return URL
// Bloque sends webhook notification when payment is confirmed
// 9. Handle webhook notification
// POST to https://yourapp.com/webhooks/pse
// {
// "orderId": "ord-123",
// "status": "completed",
// "fromAmount": "10000000",
// "toAmount": "1000000"
// }
Handling Callbacks
When the user completes their PSE payment, Bloque sends webhook notifications:
// Express.js example
app . post ( '/webhooks/pse' , async ( req , res ) => {
const { orderId , status , fromAmount , toAmount } = req . body ;
if ( status === 'completed' ) {
// Payment successful
console . log ( `Order ${ orderId } completed` );
console . log ( `User paid ${ fromAmount } , received ${ toAmount } ` );
// Update your database, notify user, etc.
await updateOrderStatus ( orderId , 'completed' );
await notifyUser ( orderId , 'Your payment was successful!' );
} else if ( status === 'failed' ) {
// Payment failed
console . log ( `Order ${ orderId } failed` );
await updateOrderStatus ( orderId , 'failed' );
await notifyUser ( orderId , 'Payment failed. Please try again.' );
}
res . status ( 200 ). send ( 'OK' );
});
User Experience Tips
Display the fee breakdown before the user confirms: const rate = rates . rates [ 0 ];
console . log ( 'Amount: 100,000.00 COP' );
console . log ( 'Fee breakdown:' );
for ( const component of rate . fee . components ) {
if ( component . type === 'percentage' ) {
console . log ( ` ${ component . name } : ${ component . percentage * 100 } %` );
} else if ( component . type === 'fixed' ) {
console . log ( ` ${ component . name } : ${ component . amount / 100 } COP` );
}
}
console . log ( `Total fee: ${ rate . fee . value / 100 } COP` );
console . log ( `You receive: ${ ( 100000 * rate . ratio ) / 1000000 } DUSD` );
Check amount limits before creating orders: const [ minFrom , maxFrom ] = rate . fromLimits ;
const userAmount = '10000000' ;
if ( BigInt ( userAmount ) < BigInt ( minFrom )) {
throw new Error ( `Minimum amount is ${ Number ( minFrom ) / 100 } COP` );
}
if ( BigInt ( userAmount ) > BigInt ( maxFrom )) {
throw new Error ( `Maximum amount is ${ Number ( maxFrom ) / 100 } COP` );
}
Present banks in a user-friendly way: const banks = await user . swap . pse . banks ();
// Group popular banks first
const popularCodes = [ '1007' , '1051' , '1001' ]; // Bancolombia, Davivienda, Bogotá
const popular = banks . banks . filter ( b => popularCodes . includes ( b . code ));
const others = banks . banks . filter ( b => ! popularCodes . includes ( b . code ));
console . log ( 'Popular banks:' );
popular . forEach ( b => console . log ( ` ${ b . name } ` ));
console . log ( 'Other banks:' );
others . forEach ( b => console . log ( ` ${ b . name } ` ));
Store order details before redirecting: const result = await user . swap . pse . create ({ ... });
// Save to database
await db . orders . create ({
orderId: result . order . id ,
userId: 'user-123' ,
fromAmount: result . order . fromAmount ,
toAmount: result . order . toAmount ,
status: result . order . status ,
checkoutUrl: result . execution ?. result . how ?. url ,
createdAt: new Date ()
});
// Then redirect
window . location . href = result . execution . result . how . url ;
Error Handling
import {
BloqueAPIError ,
BloqueValidationError ,
BloqueRateLimitError
} from '@bloque/sdk' ;
try {
const result = await user . swap . pse . create ({ ... });
} catch ( error ) {
if ( error instanceof BloqueValidationError ) {
// Invalid parameters
console . error ( 'Validation error:' , error . message );
// Show user-friendly error message
} else if ( error instanceof BloqueRateLimitError ) {
// Rate limited
console . error ( 'Too many requests, try again later' );
} else if ( error instanceof BloqueAPIError ) {
// Other API error
console . error ( 'API error:' , error . requestId );
console . error ( 'Message:' , error . message );
} else {
// Unknown error
console . error ( 'Unexpected error:' , error );
}
}
Complete Example
See the full working example:
// From: examples/swap/pse-topup.ts
import { SDK } from '@bloque/sdk' ;
const bloque = new SDK ({
origin: process . env . ORIGIN ! ,
auth: {
type: 'apiKey' ,
apiKey: process . env . API_KEY ! ,
},
mode: 'sandbox' ,
platform: 'node' ,
});
const user = await bloque . connect ( 'nestor' );
const rates = await user . swap . findRates ({
fromAsset: 'COP/2' ,
toAsset: 'DUSD/6' ,
fromMediums: [ 'pse' ],
toMediums: [ 'kusama' ],
amountSrc: '1000000' ,
});
console . log ( 'Available swap rates:' , rates . rates [ 0 ]);
if ( rates . rates . length === 0 ) {
throw new Error (
'No swap rates available for the specified assets and mediums.' ,
);
}
const banks = await user . swap . pse . banks ();
console . log ( 'Available PSE banks:' , banks . banks );
const result = await user . swap . pse . create ({
rateSig: rates . rates [ 0 ] ! ?. sig ,
toMedium: 'kusama' ,
amountSrc: '1000000' ,
depositInformation: {
urn: 'did:bloque:account:card:usr-xxx:crd-xxx' ,
},
args: {
bankCode: banks . banks [ 0 ] ! ?. code ,
userType: 0 ,
customerEmail: '[email protected] ' ,
userLegalIdType: 'CC' ,
userLegalId: '123456789' ,
customerData: {
fullName: 'John Doe' ,
phoneNumber: '3001234567'
},
},
});
console . log ( 'PSE Top-up order created:' , result . execution ?. result . how ?. url );
Next Steps
Exchange Rates Learn more about finding and using exchange rates
Bank Transfers Cash out to Colombian banks