package payment import ( "time" "cloud.o-forge.io/core/oc-lib/models/common/pricing" ) type ScheduleStatus int const ( SCHEDULE_ACTIVE ScheduleStatus = iota SCHEDULE_PAUSED // mis en pause manuellement SCHEDULE_CANCELLED // résilié SCHEDULE_COMPLETED // terminé normalement (abonnement expiré) SCHEDULE_FAILED // trop d'échecs consécutifs ) func (s ScheduleStatus) String() string { return [...]string{"active", "paused", "cancelled", "completed", "failed"}[s] } // PaymentSchedule pilote la récurrence des paiements d'un abonnement. type PaymentSchedule struct { SubscriptionID string `json:"subscription_id" bson:"subscription_id"` Frequency pricing.PaymentType `json:"frequency" bson:"frequency"` // PAY_EVERY_WEEK / PAY_EVERY_MONTH / PAY_EVERY_YEAR Amount float64 `json:"amount" bson:"amount"` Currency string `json:"currency" bson:"currency"` Status ScheduleStatus `json:"status" bson:"status"` NextPaymentDate time.Time `json:"next_payment_date" bson:"next_payment_date"` LastExecutedAt *time.Time `json:"last_executed_at,omitempty" bson:"last_executed_at,omitempty"` FailureCount int `json:"failure_count" bson:"failure_count"` MaxRetries int `json:"max_retries" bson:"max_retries" default:"3"` } // nextDate calcule la prochaine date selon la fréquence. func (ps *PaymentSchedule) nextDate() time.Time { switch ps.Frequency { case pricing.PAY_EVERY_WEEK: return ps.NextPaymentDate.AddDate(0, 0, 7) case pricing.PAY_EVERY_MONTH: return ps.NextPaymentDate.AddDate(0, 1, 0) case pricing.PAY_EVERY_YEAR: return ps.NextPaymentDate.AddDate(1, 0, 0) } return ps.NextPaymentDate } // Advance enregistre l'exécution réussie et avance à la prochaine échéance. func (ps *PaymentSchedule) Advance() { now := time.Now().UTC() ps.LastExecutedAt = &now ps.FailureCount = 0 ps.NextPaymentDate = ps.nextDate() } // RecordFailure incrémente le compteur d'échecs et désactive après MaxRetries. func (ps *PaymentSchedule) RecordFailure() { ps.FailureCount++ if ps.MaxRetries > 0 && ps.FailureCount >= ps.MaxRetries { ps.Status = SCHEDULE_FAILED } } // IsDue retourne vrai si le paiement est dû maintenant. func (ps *PaymentSchedule) IsDue() bool { return ps.Status == SCHEDULE_ACTIVE && !time.Now().UTC().Before(ps.NextPaymentDate) } // Pause suspend temporairement le calendrier. func (ps *PaymentSchedule) Pause() { if ps.Status == SCHEDULE_ACTIVE { ps.Status = SCHEDULE_PAUSED } } // Resume réactive un calendrier mis en pause. func (ps *PaymentSchedule) Resume() { if ps.Status == SCHEDULE_PAUSED { ps.Status = SCHEDULE_ACTIVE } }