Prélèvement test de mandat (1 €)¶
Extrait du domain map API, qui était chargé dans chaque session agent. La rule garde l'index module → contexte ; le détail vit ici, à côté du code.
Statuts blocking new attempt (index unique in-flight) :
- pendingSchedule = row créée, job Graphile pas encore exécuté
- scheduled = SDD LW créé (money-in attendu)
confirmed = money-in LW success (terminal pour cet attempt) — ne bloque plus un nouvel attempt au niveau DB (changement de mandat). Pendant la fenêtre chargeback, status_view reste confirmed mais toTestBankDebitView expose inProgress / canSchedule: false. Après succès (success), un nouvel attempt est autorisé.
Flow :
- Row #1 pendingSchedule + job Graphile project-test-bank-debit (execution J+3 via businessRules.testBankDebit.executionDelayDays) ; peut tourner avant creation echeancier (v1 accepte : SDD prioritaire, wire sans mandat en pratique)
- Result : Ok = succès réel (SDD programmé / refund done / déjà terminal success). Err(code) partout ailleurs (pas de mandat, fenêtre ouverte, LW fail). Le caller décide du retry : throw Graphile seulement si isRetryableScheduleError (could-not-fetch-mandates, duplicate ref 348 sans reconcile) ; codes terminaux déjà persistés (no-usable-mandate, lw-schedule-failed, …) → pas de throw. Dernier attempt Graphile : could-not-fetch-mandates → failed + Slack ; duplicate-reference-not-reconciled → Slack seulement, row reste pendingSchedule (SDD LW probablement déjà créé — ne pas libérer l'index unique). Statut skippedWirePaymentMode : plus écrit au schedule tant que le skip auto est off (rows legacy encore lisibles).
- Email schedule : transactional CIO TestBankDebitScheduled (POST send/email). Id : 14 (tous les envs). Liquid : propertyId, propertyName, ibanForDirectDebit masqué. Persist scheduled puis envoi via ProjectTestBankDebitScheduledEmailService.ensureSent (sendScheduledEmail → sendTransactionalEmail). Flag JSON emailSentAt sur scheduled / confirmed.
- scheduled → confirmed dès money-in LW success (confirmedAtLemonway = date de credit LW, pas la date du poll cron) — pas de statut mid-window
- Fenêtre chargeback dérivée : chargebackWindowCloseAt = confirmedAtLemonway + businessRules.testBankDebit.chargebackWindowDays (10j, TODO confirmer avec Angéline / type de mandat) — pendant la fenêtre le statut reste confirmed, le sync ne fait que surveiller refunds → failed
- Schedule SDD même si echeancierConfig.paymentMode.mode === 'wire' (skip auto skippedWirePaymentMode temporairement retiré : le test manuel BO tourne indépendamment du mode échéancier). À réactiver une fois le flow éprouvé après plusieurs créations manuelles de prélèvement test, quand on juge le système stable. Mandat absent → row failed + Err('no-usable-mandate') (Slack dédié mandat)
- Money-out 1€ (businessRules.testBankDebit.amountCents) via cron retryMoneyOutsForConfirmedAttempts seulement quand fenêtre chargeback fermée (Confirmed) ; backfill synthétique phase 1 = confirmed + moneyOut.skippedSyntheticBackfill (pas de money-out, compte comme succès mensuel) ; moneyOut.completed uniquement si withdraw LW success ; pending/validationPending → Ok (retry idempotent, ref LW test-debit-refund-{uuidNoDash}) ; routing impossible (mandat/IBAN) → failedNoIbanMatch (pas de retry quotidien) ; erreurs LW/API → failedAtProvider + Err ; retry quotidien sauf failureReason permanentes (spv-lemonway-wallet-not-found, lw-mandate-id-missing-on-confirmed-attempt) ; SDD duplicate ref (348) sans reconcile → Err Graphile (retry), pas failed
- Garde-fou mensuel toujours commenté dans echeancier-program-echeances-of-month (filtre projectIdsWithSuccessfulTestBankDebit) — les mensualités SDD se programment comme avant, même sans test 1€. Recap Slack seulement (echeancesBlockedUntilTestBankDebitSuccess). Le BO admin program-unscheduled-mensualite-payment n’a pas ce guard. Ne pas réactiver tant que la page BO n’est pas en prod.
- Cron sync test : echeancier-test-bank-debit-status-update via createCronTask (daily 11:00, réactivé) → sync LW (scheduled | Confirmed dans chargebackResyncWindowDays) + resume email emailSentAt seulement si encore scheduled + retry money-out après fenêtre ; money-in LW introuvable / ambigu → Err('money-in-not-found') + alerte internal-production-alerts (pas de failed) ; erreurs LW transitoires → Err + recap Slack #suivi-prelevement-test + retry lendemain
- Slack : canal prod #suivi-prelevement-test (suivi-prelevement-test dans channels.slack.ts) — notif sur premier confirmed + recap erreurs cron
- Retry admin : nouvel attempt + nouveau job (pas de mutation d'un attempt passé). canSchedule est vrai pour notStarted | failed | success ; faux seulement si inProgress (pending, scheduled, ou confirmed encore dans la fenêtre chargeback). confirmed synthétique (hasSuccessfulMensualiteBankDebit) seulement s’il n’existe encore aucune row et que le dernier SDD mensualité executed est hors BANK_DEBIT_PAYOUT_HOLD_BUSINESS_DAYS (7 j calendaires Paris, même constante que le hold payout). Un retry après failed ou success, ou un Lancer pendant le hold, enqueue un vrai SDD 1€. Recap Slack : même SQL d’exemption mensualité. Garde mensuelle / recap Slack / BO : dernier attempt = createdAt (money-out peut encore muter un confirmed précédent). Liste BO GET /administration/project/test-bank-debits : pagination serveur (offset/take/q/status/sort, { items, totalCount }). Le statut filtré/trié en SQL reprend toTestBankDebitView (confirmed hors chargeback = success).