Skip to main content
Unlock the African market for your SaaS product. FYATU enables you to accept subscription payments via local payment methods that your African customers actually use.

The Challenge

SaaS companies targeting African markets encounter:
  • Low card penetration - Most potential customers don’t have international cards
  • Payment failures - African cards often declined by international processors
  • Currency issues - Customers prefer paying in local currencies
  • Churn from payment failures - Involuntary churn due to payment method issues
  • Complex billing - Managing subscriptions across different payment methods

The FYATU Solution

Fyatu Payments

Accept payments from 1M+ Fyatu users paying from their wallets

Subscription Billing

Recurring payment support with retry logic

Multi-Currency

Price in local currencies, receive USD

Payouts

Pay African affiliates and partners to their Fyatu wallets

Subscription Integration

Create Subscription Payment

// New subscription signup
async function createSubscription(user, plan) {
  const subscription = await Subscription.create({
    userId: user.id,
    planId: plan.id,
    amount: plan.price,
    currency: 'USD',
    interval: plan.interval, // monthly, yearly
    status: 'pending_payment'
  });

  // Create payment for first billing cycle
  const collection = await fyatu.collections.create({
    amount: plan.price,
    currency: 'USD',
    reference: `SUB-${subscription.id}-1`,
    description: `${plan.name} - ${plan.interval} subscription`,
    customer: {
      email: user.email,
      name: user.fullName,
      externalId: user.id
    },
    metadata: {
      subscriptionId: subscription.id,
      planId: plan.id,
      billingCycle: 1,
      type: 'subscription'
    },
    redirectUrl: `https://app.yoursaas.com/subscription/activated`,
    webhookUrl: 'https://api.yoursaas.com/webhooks/fyatu'
  });

  return {
    subscription,
    checkoutUrl: collection.checkoutUrl
  };
}

Handle Subscription Activation

app.post('/webhooks/fyatu', async (req, res) => {
  const event = req.body;

  if (event.type === 'collection.completed') {
    const { subscriptionId, billingCycle } = event.data.metadata;

    if (subscriptionId) {
      const subscription = await Subscription.findById(subscriptionId);

      if (billingCycle === 1) {
        // First payment - activate subscription
        subscription.status = 'active';
        subscription.activatedAt = new Date();
        subscription.currentPeriodStart = new Date();
        subscription.currentPeriodEnd = calculatePeriodEnd(subscription.interval);

        // Grant access
        await grantPlanAccess(subscription.userId, subscription.planId);

        // Send welcome email
        await sendWelcomeEmail(subscription.userId);
      } else {
        // Renewal payment
        subscription.currentPeriodStart = new Date();
        subscription.currentPeriodEnd = calculatePeriodEnd(subscription.interval);

        // Extend access
        await extendAccess(subscription.userId, subscription.currentPeriodEnd);
      }

      subscription.lastPaymentAt = new Date();
      subscription.lastPaymentId = event.data.id;
      await subscription.save();
    }
  }

  res.status(200).send('OK');
});

Renewal Reminders

// Daily job: send renewal reminders
async function sendRenewalReminders() {
  const expiringIn3Days = await Subscription.findAll({
    where: {
      status: 'active',
      currentPeriodEnd: {
        [Op.between]: [
          addDays(new Date(), 2),
          addDays(new Date(), 3)
        ]
      }
    }
  });

  for (const subscription of expiringIn3Days) {
    const user = await User.findById(subscription.userId);
    const plan = await Plan.findById(subscription.planId);

    // Create renewal payment link
    const collection = await fyatu.collections.create({
      amount: plan.price,
      currency: 'USD',
      reference: `SUB-${subscription.id}-${subscription.billingCycle + 1}`,
      description: `${plan.name} renewal`,
      customer: { email: user.email },
      metadata: {
        subscriptionId: subscription.id,
        billingCycle: subscription.billingCycle + 1,
        type: 'subscription_renewal'
      },
      expiresAt: subscription.currentPeriodEnd.toISOString()
    });

    // Send reminder email with payment link
    await sendRenewalReminder(user.email, {
      planName: plan.name,
      amount: plan.price,
      renewalDate: subscription.currentPeriodEnd,
      paymentLink: collection.checkoutUrl
    });
  }
}

Handle Expired Subscriptions

// Daily job: handle expired subscriptions
async function handleExpiredSubscriptions() {
  const expiredSubscriptions = await Subscription.findAll({
    where: {
      status: 'active',
      currentPeriodEnd: { [Op.lt]: new Date() }
    }
  });

  for (const subscription of expiredSubscriptions) {
    // Grace period: 3 days
    const gracePeriodEnd = addDays(subscription.currentPeriodEnd, 3);

    if (new Date() > gracePeriodEnd) {
      // Suspend access
      subscription.status = 'past_due';
      await subscription.save();

      await revokeAccess(subscription.userId);
      await sendSubscriptionExpiredEmail(subscription.userId);
    }
  }
}

Pricing Tiers

Offer different pricing for African markets:
const plans = {
  starter: {
    name: 'Starter',
    prices: {
      USD: 29,
      KES: 3500,  // ~20% discount
      NGN: 35000, // ~20% discount
      CDF: 70000  // ~20% discount
    }
  },
  professional: {
    name: 'Professional',
    prices: {
      USD: 99,
      KES: 12000,
      NGN: 120000,
      CDF: 240000
    }
  },
  enterprise: {
    name: 'Enterprise',
    prices: {
      USD: 299,
      KES: 35000,
      NGN: 350000,
      CDF: 700000
    }
  }
};

// Get price for user's region
function getPriceForUser(plan, userCurrency) {
  return plans[plan].prices[userCurrency] || plans[plan].prices.USD;
}

One-Time Purchases

For lifetime deals or add-ons:
// Sell lifetime license
async function purchaseLifetimeLicense(user, product) {
  const collection = await fyatu.collections.create({
    amount: product.price,
    currency: 'USD',
    reference: `LTD-${user.id}-${product.id}`,
    description: `${product.name} - Lifetime License`,
    customer: {
      email: user.email,
      externalId: user.id
    },
    metadata: {
      productId: product.id,
      type: 'lifetime_deal'
    }
  });

  return collection.checkoutUrl;
}

// Purchase add-on
async function purchaseAddon(user, subscription, addon) {
  const collection = await fyatu.collections.create({
    amount: addon.price,
    currency: 'USD',
    reference: `ADDON-${subscription.id}-${addon.id}`,
    description: `Add-on: ${addon.name}`,
    customer: { email: user.email },
    metadata: {
      subscriptionId: subscription.id,
      addonId: addon.id,
      type: 'addon'
    }
  });

  return collection.checkoutUrl;
}

Refunds

Handle subscription refunds:
async function refundSubscription(subscriptionId, reason) {
  const subscription = await Subscription.findById(subscriptionId);
  const lastPayment = await Payment.findById(subscription.lastPaymentId);

  // Calculate prorated refund
  const daysUsed = daysBetween(subscription.currentPeriodStart, new Date());
  const totalDays = daysBetween(subscription.currentPeriodStart, subscription.currentPeriodEnd);
  const refundAmount = lastPayment.amount * (1 - daysUsed / totalDays);

  const refund = await fyatu.refunds.create(lastPayment.collectionId, {
    amount: Math.round(refundAmount * 100) / 100,
    reason: reason,
    reference: `REF-${subscriptionId}-${Date.now()}`
  });

  // Cancel subscription
  subscription.status = 'cancelled';
  subscription.cancelledAt = new Date();
  await subscription.save();

  // Revoke access
  await revokeAccess(subscription.userId);

  return refund;
}

Affiliate Payouts

Pay African affiliates for referrals:
// Monthly affiliate payout
async function processAffiliatePayouts() {
  const affiliates = await Affiliate.findAll({
    where: { pendingPayout: { [Op.gt]: 0 } }
  });

  for (const affiliate of affiliates) {
    if (affiliate.pendingPayout >= 50) { // Minimum payout
      try {
        const payout = await fyatu.payouts.create({
          amount: affiliate.pendingPayout,
          currency: 'USD',
          reference: `AFF-${affiliate.id}-${Date.now()}`,
          description: 'Affiliate commission payout',
          recipient: {
            accountId: affiliate.fyatuAccountId
          },
          metadata: {
            affiliateId: affiliate.id,
            referralCount: affiliate.monthlyReferrals
          }
        });

        affiliate.pendingPayout = 0;
        affiliate.lastPayoutAt = new Date();
        affiliate.lastPayoutId = payout.id;
        await affiliate.save();
      } catch (error) {
        console.error(`Affiliate payout failed: ${affiliate.id}`, error);
      }
    }
  }
}

Why Fyatu for SaaS?

BenefitDescription
1M+ UsersAccess Fyatu’s growing user base across Africa
Instant PaymentsCustomers pay from their Fyatu wallet in seconds
No Failed CardsWallet payments don’t fail like card payments
Reduced ChurnReliable payments reduce involuntary churn
Affiliate PayoutsPay affiliates directly to their Fyatu wallets
Fyatu users can fund their wallets using various local payment methods, giving your SaaS access to African customers without managing multiple payment integrations.

Best Practices

1. Promote Fyatu as a Payment Option

Highlight Fyatu as a reliable payment method for your African customers. Users can fund their Fyatu wallet using their preferred local payment method and pay you seamlessly.

2. Send Payment Reminders

  • 7 days before renewal
  • 3 days before renewal
  • Day of renewal
  • After grace period expires

3. Reduce Involuntary Churn

  • Multiple payment attempts
  • Grace periods for expired subscriptions
  • Clear renewal payment links
  • Easy account management

Getting Started

1

Sign Up

2

Configure Collection App

Set up your app for subscription payments
3

Integrate

Add FYATU to your subscription flow
4

Test

Test subscription and renewal flows
5

Launch

Start accepting payments from African customers

Resources