Les subscriptions natives ne sont pas encore dans l’API (V2). Pour le moment, gérez le cycle d’abonnement côté votre app et créez un PaymentIntent à chaque renouvellement.

Pattern actuel

[Votre app] tient le calendrier des abonnements
J-3 expiration  → notify_email("Renouvellement prochain")
J expiration    → izipay.paymentIntents.create({ ... })
                → email avec lien de paiement
                → si payé sous 7j → renew + reset date
                → si pas payé → désactiver compte

Implémentation

1. Modèle DB

CREATE TABLE subscriptions (
  id UUID PRIMARY KEY,
  user_id UUID NOT NULL,
  plan VARCHAR NOT NULL,
  amount_xof INT NOT NULL,
  period VARCHAR NOT NULL,        -- monthly | yearly
  current_period_end TIMESTAMPTZ NOT NULL,
  status VARCHAR NOT NULL,        -- active | past_due | cancelled
  last_intent_id VARCHAR
);

2. Cron de renouvellement

// daily — pour chaque subscription expirant aujourd'hui
async function renewSubscriptionsDueToday() {
  const subs = await db.subscriptions.findMany({
    where: { currentPeriodEnd: { lte: tomorrow() }, status: 'active' },
  });

  for (const sub of subs) {
    const intent = await izipay.paymentIntents.create({
      requestedCurrencyType: 'fiat',
      currencyRequested: 'XOF',
      amountRequested: String(sub.amountXof),
      acceptedCoins: ['USDT.TRC20', 'USDT.BEP20'],
      merchantReference: `sub-${sub.id}-${sub.currentPeriodEnd.toISOString()}`,
      idempotencyKey: `renew-${sub.id}-${sub.currentPeriodEnd.toISOString()}`,
      expiresAt: addDays(now(), 7),
      metadata: { subscriptionId: sub.id, period: sub.period },
    });

    await db.subscriptions.update({
      id: sub.id, status: 'past_due', lastIntentId: intent.id,
    });

    await sendRenewalEmail({
      to: sub.user.email,
      paymentUrl: intent.paymentUrl,
      expiresAt: intent.expiresAt,
    });
  }
}

3. Webhook handler

case 'payment_intent.completed': {
  const { intentId, merchantReference, metadata } = event.data;
  if (metadata?.subscriptionId) {
    const sub = await db.subscriptions.findById(metadata.subscriptionId);
    if (sub?.lastIntentId === intentId && sub.status === 'past_due') {
      await db.subscriptions.update({
        id: sub.id,
        status: 'active',
        currentPeriodEnd: addPeriod(sub.currentPeriodEnd, sub.period),
        lastIntentId: null,
      });
    }
  }
  break;
}

case 'payment_intent.expired': {
  const { merchantReference, metadata } = event.data;
  if (metadata?.subscriptionId) {
    const sub = await db.subscriptions.findById(metadata.subscriptionId);
    if (sub?.status === 'past_due') {
      await db.subscriptions.update({ id: sub.id, status: 'cancelled' });
      await deactivateAccount(sub.userId);
    }
  }
  break;
}

Alternative : Products + email manuel

Pour un onboarding rapide d’abonnés sans cron, créez un Product par plan et partagez son lien de paiement public (récupéré à la création du produit côté dashboard ou API). Le client revient chaque mois cliquer. Pas vraiment “subscription” mais ça marche pour les early stages.

Limites du pattern actuel

  • Pas de re-essai automatique d’une carte / wallet pour le client (le client doit revenir cliquer le lien)
  • Pas de notification “votre abonnement va expirer dans X jours” intégrée (à coder côté votre app)
  • Pas de gestion native des promo codes (à gérer côté votre app, créez l’intent avec le montant promo)
Subscriptions V2 : prévu T1 2027 avec API dédiée (POST /v1/subscriptions, gestion native du cycle, promo codes, multi-currency).