Bonaventure OgetoBy Bonaventure Ogeto|

Splitting Payments Between a School and Its Departments

Create a Paystack subaccount for each department that has its own bank account. Create a Transaction Split group per fee schedule listing each department subaccount and their flat amount. When a student pays, pass the split_code for that term's fee structure. Paystack settles each department directly. Store subaccount_codes in a departments table keyed by fee_type.

Fee Structure and Split Group

// Fee structure: NGN 50,000 total
// Tuition: 40,000 (main account)
// Library: 2,000 (library subaccount)
// Sports: 3,000 (sports subaccount)
// Lab: 5,000 (science lab subaccount)

async function createTermSplitGroup(term) {
  var feeStructure = await db.feeStructures.findByTerm(term);
  var subaccounts = [];

  for (var dept of feeStructure.departments) {
    var department = await db.departments.findById(dept.department_id);
    subaccounts.push({
      subaccount: department.subaccount_code,
      share: dept.amount * 100, // kobo
    });
  }

  var response = await fetch('https://api.paystack.co/split', {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      name: term + ' fee split',
      type: 'flat',
      currency: 'NGN',
      subaccounts,
      bearer_type: 'account',
    }),
  });
  var data = await response.json();

  // Store split code for this term
  await db.feeStructures.update(
    { split_code: data.data.split_code },
    { where: { term } }
  );

  return data.data.split_code;
}

// Student payment checkout
app.post('/api/fees/pay', async (req, res) => {
  var { studentId, term, email } = req.body;
  var feeStructure = await db.feeStructures.findByTerm(term);

  var response = await fetch('https://api.paystack.co/transaction/initialize', {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email,
      amount: feeStructure.total_amount * 100,
      split_code: feeStructure.split_code,
      metadata: { student_id: studentId, term },
    }),
  });
  var data = await response.json();
  res.json({ authorization_url: data.data.authorization_url });
});

Learn More

Key Takeaways

  • Create a department subaccount per department with its own bank account (library fund, sports levy, PTA, etc.).
  • Build a fee_structures table: for each term/year, store the breakdown of fees per department.
  • Create a Transaction Split group per fee structure. Reuse the same split_code for all students in that term.
  • The school's main tuition amount goes to your main Paystack account (the residual after subaccount shares).
  • On charge.success, mark the student's fee as paid and generate a receipt with the full fee breakdown.
  • Offer parents/students a consolidated payment portal — one payment, all departments settled automatically.

Frequently Asked Questions

Can I reuse the same split group for all students in a term?
Yes. If the fee amounts are the same for all students in a class or level, create one split group per fee structure and reuse its split_code. Only create new split groups when the fee breakdown changes (different term, different class level, special levies).
What if a department does not have its own bank account?
Only include departments with separate bank accounts in the split. For departments without their own accounts, add their fee to the school's main account amount (the residual). The school bursar can then transfer internally to those departments through normal budget allocation.
How do I generate a fee receipt showing the full breakdown?
Store the fee_structure breakdown in your database. On charge.success, look up the fee structure for the transaction term and generate a receipt itemizing each component. The Paystack transaction receipt only shows the total — you must build the line-item receipt yourself from your fee structure table.
Can parents pay partial fees and complete later?
Paystack does not natively support partial payments on a single invoice. Implement partial payments by letting parents pay one fee category at a time (a separate transaction per fee type), or track a balance owed in your database and allow multiple payments against the same student account until fully settled.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon