Overview
The first step in integrating mobile top-ups is to fetch the list of available plans from the API. Plans include all operators (Mobilis, Djezzy, Ooredoo, Pixx) with both dynamic and fixed amount options.
Plans are stable and rarely change. Cache them in your database to reduce API calls and improve performance.
API Reference View complete API documentation for the GET /v3/mobile/plans endpoint
Understanding Plan Types
Dynamic Plans
Dynamic plans allow variable amounts within a min/max range:
{
"code" : "PREPAID_DJEZZY" ,
"name" : "Djezzy Prepaid" ,
"operator" : "Djezzy" ,
"cost" : 0.98 ,
"isEnabled" : true ,
"min_amount" : 50 ,
"max_amount" : 5000
}
Use cases:
Prepaid top-ups (credit)
Postpaid bill payments (facture)
International calling credit
Fixed Plans
Fixed plans have predetermined amounts:
{
"code" : "PIXX_500" ,
"name" : "Pixx 500 DA" ,
"operator" : "Pixx" ,
"cost" : 0.97 ,
"isEnabled" : true ,
"amount" : 500
}
Use cases:
Special promotional offers
Data packs
Service activations
Special plans that retrieve available offers for a specific number:
{
"code" : "GETMENU_Mobilis" ,
"name" : "Mobilis GetMenu" ,
"operator" : "Mobilis" ,
"cost" : 0.98 ,
"isEnabled" : true ,
"min_amount" : 50 ,
"max_amount" : 5000
}
Returns: Array of suggested_offers with all available plans for that specific number.
Fetching Plans from API
Basic Request
curl https://api.oneclickdz.com/v3/mobile/plans \
-H "X-Access-Token: YOUR_API_KEY"
Response Structure
{
"success" : true ,
"data" : {
"dynamicPlans" : [
{
"code" : "PREPAID_MOBILIS" ,
"name" : "📱 PREPAID | عادي" ,
"operator" : "Mobilis" ,
"cost" : 0.96 ,
"isEnabled" : true ,
"min_amount" : 40 ,
"max_amount" : 3999
},
{
"code" : "GROS_MOBILIS" ,
"name" : "📱 GROS MOBILIS | جملة" ,
"operator" : "GMobilis" ,
"isEnabled" : true ,
"min_amount" : 5000 ,
"max_amount" : 300000
}
],
"fixedPlans" : [
{
"code" : "MIX500_MOBILIS" ,
"name" : "📱🌐 AUTO | MIX 500" ,
"operator" : "Mobilis" ,
"isEnabled" : true ,
"cost" : 0.96 ,
"amount" : 500
},
{
"code" : "MIX1000_OOREDOO" ,
"name" : "📱🌐 AUTO | MIX 1000" ,
"operator" : "Ooredoo" ,
"isEnabled" : true ,
"cost" : 0.99 ,
"amount" : 1000
}
]
},
"meta" : {
"timestamp" : "2025-10-29T00:35:59.220Z"
}
}
Understanding the Cost Field
The cost field represents your wholesale price multiplier:
How It Works
Calculating Sell Price
Important Note
Example: cost = 0.98 For a 1000 DZD top-up:
Face value: 1000 DZD (what user receives)
Your cost: 1000 × 0.98 = 980 DZD (what you pay)
Your margin: 1000 - 980 = 20 DZD (2%)
Caching Plans in Database
Since plans rarely change, cache them locally with your pricing:
Database Schema
CREATE TABLE mobile_plans (
id SERIAL PRIMARY KEY ,
code VARCHAR ( 100 ) UNIQUE NOT NULL ,
name VARCHAR ( 255 ) NOT NULL ,
operator VARCHAR ( 50 ) NOT NULL ,
type VARCHAR ( 20 ) NOT NULL , -- 'dynamic' or 'fixed'
-- Cost info
wholesale_cost DECIMAL ( 5 , 4 ) NOT NULL ,
sell_price_multiplier DECIMAL ( 5 , 4 ) NOT NULL ,
-- Amount limits
min_amount INTEGER ,
max_amount INTEGER ,
fixed_amount INTEGER ,
-- Status
is_enabled BOOLEAN DEFAULT true,
is_visible BOOLEAN DEFAULT true,
-- Metadata
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_mobile_plans_operator ON mobile_plans(operator);
CREATE INDEX idx_mobile_plans_enabled ON mobile_plans(is_enabled);
CREATE INDEX idx_mobile_plans_code ON mobile_plans(code);
Sync Function
const db = require ( './database' );
async function syncMobilePlans () {
try {
console . log ( '🔄 Syncing mobile plans...' );
// Fetch plans from API
const plans = await fetchMobilePlans ();
// Calculate your pricing
const PROFIT_MARGIN = 0.05 ; // 5% profit
for ( const plan of plans ) {
const isFixed = !! plan . amount ;
const planData = {
code: plan . code ,
name: plan . name ,
operator: plan . operator ,
type: isFixed ? 'fixed' : 'dynamic' ,
wholesaleCost: plan . cost ,
sellPriceMultiplier: plan . cost * ( 1 + PROFIT_MARGIN ),
minAmount: plan . min_amount || null ,
maxAmount: plan . max_amount || null ,
fixedAmount: plan . amount || null ,
isEnabled: plan . isEnabled ,
lastSyncedAt: new Date ()
};
// Upsert (insert or update)
await db . mobilePlans . upsert ({
where: { code: plan . code },
update: planData ,
create: planData
});
}
console . log ( `✅ Successfully synced ${ plans . length } plans` );
// Log statistics
const stats = {
total: plans . length ,
dynamic: plans . filter ( p => ! p . amount ). length ,
fixed: plans . filter ( p => p . amount ). length ,
enabled: plans . filter ( p => p . isEnabled ). length
};
console . log ( '📊 Plan statistics:' , stats );
return stats ;
} catch ( error ) {
console . error ( '❌ Failed to sync plans:' , error );
throw error ;
}
}
// Run sync on startup
syncMobilePlans ();
// Schedule daily sync at midnight
const cron = require ( 'node-cron' );
cron . schedule ( '0 0 * * *' , () => {
console . log ( '⏰ Running scheduled plan sync...' );
syncMobilePlans ();
});
Serving Plans to Users
When users request plans, serve from your database with your pricing:
async function getUserPlans ( filters = {}) {
const { operator , type , enabled = true } = filters ;
const query = { isEnabled: enabled , isVisible: true };
if ( operator ) query . operator = operator ;
if ( type ) query . type = type ;
const plans = await db . mobilePlans . findMany ({
where: query ,
orderBy: [
{ operator: 'asc' },
{ fixedAmount: 'asc' },
{ name: 'asc' }
]
});
// Transform for user display (remove wholesale cost!)
return plans . map ( plan => ({
code: plan . code ,
name: plan . name ,
operator: plan . operator ,
type: plan . type ,
minAmount: plan . minAmount ,
maxAmount: plan . maxAmount ,
amount: plan . fixedAmount ,
// Don't expose wholesale cost!
// Only expose what user needs
}));
}
// API endpoint
app . get ( '/api/mobile/plans' , async ( req , res ) => {
try {
const { operator } = req . query ;
const plans = await getUserPlans ({ operator });
res . json ({ success: true , data: { plans } });
} catch ( error ) {
res . status ( 500 ). json ({
success: false ,
error: error . message
});
}
});
Filtering Plans
By Operator
const mobilisPlans = await getUserPlans ({ operator: 'Mobilis' });
const djezzyPlans = await getUserPlans ({ operator: 'Djezzy' });
const ooredooPlans = await getUserPlans ({ operator: 'Ooredoo' });
By Phone Number
function getOperatorFromPhone ( phone ) {
const prefixMap = {
'065' : [ 'Mobilis' ],
'066' : [ 'Mobilis' , 'Ooredoo' ],
'067' : [ 'Mobilis' ],
'077' : [ 'Djezzy' , 'Mobilis' ],
'078' : [ 'Djezzy' ],
'055' : [ 'Ooredoo' , 'Djezzy' , 'Mobilis' ],
'056' : [ 'Ooredoo' ],
'068' : [ 'Pixx' ]
};
const prefix = phone . substring ( 0 , 3 );
return prefixMap [ prefix ] || [];
}
async function getPlansForPhone ( phone ) {
const operators = getOperatorFromPhone ( phone );
if ( operators . length === 0 ) {
throw new Error ( 'Invalid phone number prefix' );
}
const plans = await db . mobilePlans . findMany ({
where: {
operator: { in: operators },
isEnabled: true ,
isVisible: true
}
});
return plans ;
}
Best Practices
Always cache plans in your database. Plans rarely change, and caching dramatically improves performance.
Never expose the cost field to end users. Only show your retail pricing.
Set up a daily cron job to sync plans. This ensures you have the latest offerings and availability.
Always verify isEnabled before displaying plans. Disabled plans may be temporarily unavailable due to operator issues.
Calculate your selling price by adding a reasonable profit margin (typically 3-10%) to the wholesale cost.
Create indexes on frequently queried fields (operator, code, isEnabled) for fast lookups.
Next Steps