package dev.lions.unionflow.server.entity; import dev.lions.unionflow.server.api.enums.paiement.StatutIntentionPaiement; import dev.lions.unionflow.server.api.enums.paiement.TypeObjetIntentionPaiement; import jakarta.persistence.*; import jakarta.validation.constraints.*; import java.math.BigDecimal; import java.time.LocalDateTime; import lombok.*; /** * Hub centralisé pour tout paiement Wave initié depuis UnionFlow. * *
Flux : *
Table : {@code intentions_paiement} */ @Entity @Table( name = "intentions_paiement", indexes = { @Index(name = "idx_intention_utilisateur", columnList = "utilisateur_id"), @Index(name = "idx_intention_statut", columnList = "statut"), @Index(name = "idx_intention_wave_session", columnList = "wave_checkout_session_id", unique = true), @Index(name = "idx_intention_expiration", columnList = "date_expiration") }) @Data @NoArgsConstructor @AllArgsConstructor @Builder @EqualsAndHashCode(callSuper = true) public class IntentionPaiement extends BaseEntity { @NotNull @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "utilisateur_id", nullable = false) private Membre utilisateur; /** NULL pour les abonnements UnionFlow SA (payés par l'organisation directement) */ @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "organisation_id") private Organisation organisation; @NotNull @DecimalMin("0.01") @Digits(integer = 12, fraction = 2) @Column(name = "montant_total", nullable = false, precision = 14, scale = 2) private BigDecimal montantTotal; @NotBlank @Pattern(regexp = "^[A-Z]{3}$") @Builder.Default @Column(name = "code_devise", nullable = false, length = 3) private String codeDevise = "XOF"; @Enumerated(EnumType.STRING) @NotNull @Column(name = "type_objet", nullable = false, length = 30) private TypeObjetIntentionPaiement typeObjet; @Enumerated(EnumType.STRING) @Builder.Default @Column(name = "statut", nullable = false, length = 20) private StatutIntentionPaiement statut = StatutIntentionPaiement.INITIEE; /** ID de session Wave — clé de réconciliation sur webhook */ @Column(name = "wave_checkout_session_id", unique = true, length = 255) private String waveCheckoutSessionId; /** URL de paiement Wave à rediriger l'utilisateur */ @Column(name = "wave_launch_url", length = 1000) private String waveLaunchUrl; /** ID transaction Wave reçu via webhook */ @Column(name = "wave_transaction_id", length = 100) private String waveTransactionId; /** * JSON : liste des objets couverts par ce paiement. * Exemple : [{\"type\":\"COTISATION\",\"id\":\"uuid\",\"montant\":5000}, ...] */ @Column(name = "objets_cibles", columnDefinition = "TEXT") private String objetsCibles; @Column(name = "date_expiration") private LocalDateTime dateExpiration; @Column(name = "date_completion") private LocalDateTime dateCompletion; // ── Méthodes métier ──────────────────────────────────────────────────────── public boolean isActive() { return StatutIntentionPaiement.INITIEE.equals(statut) || StatutIntentionPaiement.EN_COURS.equals(statut); } public boolean isExpiree() { return dateExpiration != null && LocalDateTime.now().isAfter(dateExpiration); } public boolean isCompletee() { return StatutIntentionPaiement.COMPLETEE.equals(statut); } @PrePersist protected void onCreate() { super.onCreate(); if (statut == null) statut = StatutIntentionPaiement.INITIEE; if (codeDevise == null) codeDevise = "XOF"; if (dateExpiration == null) { dateExpiration = LocalDateTime.now().plusMinutes(30); } } }