Profil : VTC, livraison, plateforme de freelance. Collecte les paiements clients, reverse aux prestataires selon leur volume.

Différence avec Marketplace

Dans une marketplace, le split est par transaction (chaque commande paye son vendeur). Dans la gig economy, le split est par cycle (paie hebdo / quotidienne de TOUS les chauffeurs / livreurs accumulant leur dû).

Workflow type

Cycle = 1 semaine
J1-J7  : paiements clients collectés, balance crypto grossit
J7 23h : cron déclenche le payroll
       → calcule {prestataire → montant dû} depuis votre DB
       → une boucle de payouts unitaires (un par prestataire)
       → webhook payout.confirmed/failed → marque comme payé
       → re-essai manuel des échecs
Privilégiez les transferts internes (payouts.createInternal) quand le prestataire est aussi sur IzichangePay : gratuits et instantanés (voir plus bas). Sinon, émettez un payout unitaire par prestataire (payouts.create).

Code

// cron job — chaque lundi 23h
import { izipay } from './lib/izipay';

async function runWeeklyPayroll(week: string) {
  // 1. Calculer les dus (en crypto déjà préparée côté votre app)
  const ledger = await db.query(`
    SELECT driver_id, driver_wallet, SUM(net_to_driver) AS amount_crypto
    FROM trips
    WHERE week = $1 AND payout_status = 'pending'
    GROUP BY driver_id, driver_wallet
    ORDER BY amount_crypto DESC
  `, [week]);

  // 2. Un payout unitaire par prestataire
  for (const row of ledger) {
    await izipay.payouts.create({
      destinationAddress: row.driver_wallet,
      assetCode: 'USDT.TRC20',
      amount: row.amount_crypto.toString(),
      idempotencyKey: `payroll-${week}-${row.driver_id}`,
      note: `Payroll ${week} - driver ${row.driver_id}`,
    });
  }
}
Le payout groupé (batch) arrivera en V2. Aujourd’hui, on émet un payout par prestataire dans une boucle, chacun idempotent grâce à sa propre idempotencyKey.

Tracking par prestataire

Webhook payout.confirmed / payout.failed (un par payout) :
case 'payout.confirmed': {
  await db.trips.markPaid({
    izipayPayoutId: event.data.payoutId,
    txHash: event.data.txHash,
  });
  break;
}
case 'payout.failed': {
  await db.trips.markFailed({
    izipayPayoutId: event.data.payoutId,
    reason: event.data.failureReason,
  });
  // Notif au prestataire
  break;
}

Cas où le prestataire est aussi sur IzichangePay

Préférable : utilisez createInternal (gratuit, instantané). Le prestataire peut ensuite settle vers son mobile money lui-même (auto-settlement configuré sur son compte).
for (const line of lines) {
  await izipay.payouts.createInternal({
    assetCode: 'USDT.TRC20',
    amount: line.amount,
    destinationType: 'internal_email',
    destinationEmail: line.driverEmail,
    idempotencyKey: `payroll-${week}-${line.driverId}`,
    note: `Semaine ${week}`,
  });
}
Avantages :
  • Gratuit (0 gas)
  • Instantané (pas d’attente blockchain)
  • Le prestataire choisit lui-même son rail de cash-out (Wave, OM, MTN, IBAN, etc.) sans que vous ayez à gérer ses préférences

Limites

  • Approbation (TAP) : un payout dépassant le seuil (défaut 500 USD) passe en awaiting_approval et exige l’approbation d’un ou plusieurs administrateurs plateforme (politique Travel-rule), pas d’un membre de votre équipe. Le seuil est une politique plateforme, non self-service
  • Whitelist : si activée, chaque adresse prestataire doit avoir atteint sa maturation (24h, réglage plateforme). Recommandation : onboarding qui demande l’adresse au prestataire à l’inscription, ainsi la maturation est passée avant le premier payout
Pour les gros volumes par cycle, contactez le support : info@izichange.com.