Wholesale Fees

Institutions that white label Eclipse as a fintech enablement platform need to be able to configure wholesale fees – i.e. the fees to be recovered from tenants.

Eclipse has a highly flexible wholesale billing engine:

  • Chargeable transaction events are published to a wholesale event queue – this ensures that wholesale logic is decoupled from the primary transactions.
  • Complex logic can be configured in Javassist specific to transaction types to determine wholesale fee logic. Javassist is a flexible mechanism whereby complex business logic can be defined and compiled at a runtime – i.e. no deployment required for changes.
  • This logic includes the business rules to determine the fee amounts as well as splitting between system wallets if required (suspense accounts)
  • Wholesale fees are collected in near realtime and withdrawals of these fees to an external account can be automated on a schedule.

In order to configure wholesale fee logic the following information is required:

  1. The wallet from where tenant fees should be paid. This operational wallet can be configured to allow negative balances or minimum balance can be set to zero, mandating prefunding.
  2. The event type of this fee - currently supported types are Wallet-Transfer, PAYMENT and WITHDRAWAL
  3. The transaction type to be charged – e.g. DTB_KE_PESALINK
  4. The Javassist logic that determines the fee to be charged as well as the manner in which the fee should be split and the system wallets (suspense accounts) where those fees should be collected.

Here is an example Javassist template that allows admins to define:

  1. The destination fee wallets and fee split per wallet.
  2. The fee type: fixedAmount, fixedPercentage, TieredAmount, TieredPercentage
  3. The tier logic for tieredAmount and tieredPercentage billing
  4. An administrator can complete the sections marked complete for a particular tenant and then load as a wholesale configuration using the admin portal.
    public com.ukheshe.services.wholesalebilling.model.WholesaleBillingResult calculateWholesaleFees(jakarta.persistence.EntityManager em, long tenantId,
            java.math.BigDecimal amount,
            long debitFeeWalletId, String uniqueId,
            long transactedWalletId,
            com.ukheshe.services.wholesalebilling.listener.logic.LogicHelper helper) {

        long revShareWallet1 = 1234;	   						//TO COMPLETE
        long revShareWallet2 = 1234;						//TO COMPLETE
        long transactionCostWallet = 1234;					//TO COMPLETE
        long taxWallet = 1234;								//TO COMPLETE
        
        String feeType = "FixedAmount";									//TO COMPLETE - possibly values:  FixedAmount, FixedPercentage, TieredAmount, TieredPercentage
		
		String revShareWallet1FeeConfig = "40P";						
        String revShareWallet2FeeConfig = "40P";					
        String transactionCostWalletFeeConfig = "20P";				
		String taxWalletFeeConfig = "15P"; //15% OF ACTUAL FEE							
        
		com.ukheshe.services.wholesalebilling.model.WholesaleBillingResult result = new com.ukheshe.services.wholesalebilling.model.WholesaleBillingResult();

        java.math.BigDecimal wholesaleFee = java.math.BigDecimal.ZERO;
        java.math.BigDecimal totalFee = java.math.BigDecimal.ZERO;
		
		//LOGIC to exclude system wallets from wholesale fees
		if(transactedWalletId == revShareWallet1 || transactedWalletId == revShareWallet2 || transactedWalletId == transactionCostWallet || transactedWalletId == taxWallet) {
    		return result;
    	}

		//LOGIC FOR FixedAmount
		if ("FixedAmount".equals(feeType)) {
			String fixedAmountConfig = "10A";								//TO COMPLETE		
            wholesaleFee = helper.calculateFee(fixedAmountConfig, amount);
		//LOGIC FOR FixedPercentage
		} else if ("FixedPercentage".equals(feeType)) {
			String fixedPercentageConfig = "5P";							//TO COMPLETE		
            wholesaleFee = helper.calculateFee(fixedPercentageConfig, amount);
		//LOGIC FOR TieredAmount
		} else if ("TieredAmount".equals(feeType)) {
			if (amount.intValue() >= 0 && amount.intValue() <= 1000) {
				String tieredAmountConfig = "10A";							//TO COMPLETE		
				wholesaleFee = helper.calculateFee(tieredAmountConfig, amount);
			} else if (amount.intValue() > 1000 && amount.intValue() <= 2000) {
				String tieredAmountConfig = "20A";							//TO COMPLETE		
				wholesaleFee = helper.calculateFee(tieredAmountConfig, amount);
			} else if (amount.intValue() > 2000) {
				String tieredAmountConfig = "30A";							//TO COMPLETE		
				wholesaleFee = helper.calculateFee(tieredAmountConfig, amount);
			}
		//LOGIC FOR TieredPercentage
		} else if ("TieredPercentage".equals(feeType)) {
			if (amount.intValue() >= 0 && amount.intValue() <= 100) {
				String tieredPercentageConfig = "0P";							//TO COMPLETE		
				wholesaleFee = helper.calculateFee(tieredPercentageConfig, amount);
			} else if (amount.intValue() > 100 && amount.intValue() <= 1500) {
				String tieredPercentageConfig = "1P";							//TO COMPLETE		
				wholesaleFee = helper.calculateFee(tieredPercentageConfig, amount);
			} else if (amount.intValue() > 1500 && amount.intValue() <= 2500) {
				String tieredPercentageConfig = "3P";							//TO COMPLETE		
				wholesaleFee = helper.calculateFee(tieredPercentageConfig, amount);
			} else if (amount.intValue() > 2500 && amount.intValue() <= 3500) {
				String tieredPercentageConfig = "5P";							//TO COMPLETE		
				wholesaleFee = helper.calculateFee(tieredPercentageConfig, amount);
			} else if (amount.intValue() > 3500) {
				String tieredPercentageConfig = "6P";							//TO COMPLETE		
				wholesaleFee = helper.calculateFee(tieredPercentageConfig, amount);
			}
		}
		
		java.math.BigDecimal revShare1 = helper.calculateFee(revShareWallet1FeeConfig, wholesaleFee);
		java.math.BigDecimal revShare2 = helper.calculateFee(revShareWallet2FeeConfig, wholesaleFee);
		java.math.BigDecimal transactionCost = helper.calculateFee(transactionCostWalletFeeConfig, wholesaleFee);
		java.math.BigDecimal taxFee = helper.calculateFee(taxWalletFeeConfig, wholesaleFee);
		return helper.createWholesaleBillingResult(tenantId, debitFeeWalletId, revShareWallet1,
                revShare1, revShareWallet2, revShare2,
                transactionCostWallet, transactionCost,  taxWallet, taxFee);

    }
  1. Load the final wholesale billing configuration in the admin portal. Note this can also be loaded using the wholesale configuration endpoint.

Wholesale Fee withdrawal

Fees are collected in near real time and stored in system wallet(s) that represent suspense accounts – these wallets can be automatically cleared on a schedule to an external account. This automated scheduling is configured through property: eclipse.system.wallet.eftout.config

Scheduling withdrawals

We support the following parameters for scheduling basic sweeps based on time and the day of the week:

  • wallet{walletId}.clearance.dayOfWeek - refers to the day when a wallet withdrawal should happen. 1-7 maps to Monday to Sunday. 0 means everyday.
  • wallet{walletId}.clearance.hourOfDay - refers to the hour in 24 hour notation for when a wallet withdrawal should happen on a day. e.g. 18 means a sweep scheduled for that day will happen at 18:00.

For more advanced schedule configuration we support cron expressions. A cron expressions is an expression language that allow you to concisely define complex schedules - for more details, including examples, please refer here:

  • wallet{walletId}.clearance.cron - cron expression to determine the schedule of the withdrawal.
#globally enable or disable wallet clearing
enabled=true

#tenants with wallet clearing enabled
tenantIds=12345

#wallets to be cleared for this tenant
tenant12345.sourceWalletIds=9876,5432

#Sweep wholesale fees for Test Tenant for institution designated bank account
wallet9876.bankDetail=[{"att":"bankName", "val":"Acme"}, {"att":"accountHolderName", "val":"Acme"}, {"att":"accountNumber", "val":"12345"}, {"att":"branchCode", "val":"002"}]
wallet9876.clearance.dayOfWeek=0
wallet9876.clearance.hourOfDay=18
wallet9876.paymentReference=Eclipse wholesale fees
wallet9876.description=Clear wholesale fees

#Sweep wholesale fees for Test Tenant for institution designated bank account
wallet5432.bankDetail=[{"att":"bankName", "val":"Acme"}, {"att":"accountHolderName", "val":"Acme"}, {"att":"accountNumber", "val":"12345"}, {"att":"branchCode", "val":"002"}]
wallet5432.clearance.cron=0 * * * *
wallet5432.paymentReference=Eclipse wholesale fees
wallet5432.description=Clear wholesale fees