Payment Flow + Access Flow Change
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
|
||||
"cloud.o-forge.io/core/oc-lib/models/utils"
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
)
|
||||
|
||||
type PaymentStatus int
|
||||
|
||||
const (
|
||||
PAYMENT_PENDING PaymentStatus = iota
|
||||
PAYMENT_PROCESSING // en cours de traitement blockchain/réseau
|
||||
PAYMENT_COMPLETED // confirmé
|
||||
PAYMENT_FAILED // échoué
|
||||
PAYMENT_CANCELLED // annulé avant exécution
|
||||
PAYMENT_REFUNDED // remboursé
|
||||
)
|
||||
|
||||
func (s PaymentStatus) String() string {
|
||||
return [...]string{"pending", "processing", "completed", "failed", "cancelled", "refunded"}[s]
|
||||
}
|
||||
|
||||
func PaymentStatusList() []PaymentStatus {
|
||||
return []PaymentStatus{PAYMENT_PENDING, PAYMENT_PROCESSING, PAYMENT_COMPLETED, PAYMENT_FAILED, PAYMENT_CANCELLED, PAYMENT_REFUNDED}
|
||||
}
|
||||
|
||||
type PaymentMethod int
|
||||
|
||||
const (
|
||||
METHOD_BLOCKCHAIN PaymentMethod = iota
|
||||
METHOD_CREDIT_CARD
|
||||
METHOD_BANK_TRANSFER
|
||||
METHOD_CRYPTO
|
||||
METHOD_INTERNAL_CREDIT // crédit interne à la plateforme
|
||||
)
|
||||
|
||||
func (m PaymentMethod) String() string {
|
||||
return [...]string{"blockchain", "credit_card", "bank_transfer", "crypto", "internal_credit"}[m]
|
||||
}
|
||||
|
||||
func PaymentMethodList() []PaymentMethod {
|
||||
return []PaymentMethod{METHOD_BLOCKCHAIN, METHOD_CREDIT_CARD, METHOD_BANK_TRANSFER, METHOD_CRYPTO, METHOD_INTERNAL_CREDIT}
|
||||
}
|
||||
|
||||
// Payment représente une transaction de paiement — instantanée, mensuelle ou annuelle.
|
||||
type Payment struct {
|
||||
utils.AbstractObject
|
||||
BillID string `json:"bill_id,omitempty" bson:"bill_id,omitempty"`
|
||||
InvoiceID string `json:"invoice_id,omitempty" bson:"invoice_id,omitempty"`
|
||||
SubscriptionID string `json:"subscription_id,omitempty" bson:"subscription_id,omitempty"`
|
||||
PayerPeerID string `json:"payer_peer_id,omitempty" bson:"payer_peer_id,omitempty"`
|
||||
RecipientPeerID string `json:"recipient_peer_id,omitempty" bson:"recipient_peer_id,omitempty"`
|
||||
Amount float64 `json:"amount" bson:"amount"`
|
||||
Currency string `json:"currency" bson:"currency" default:"EUR"`
|
||||
Status PaymentStatus `json:"status" bson:"status"`
|
||||
Method PaymentMethod `json:"method" bson:"method"`
|
||||
PaymentType pricing.PaymentType `json:"payment_type" bson:"payment_type"` // PAY_ONCE, PAY_EVERY_MONTH, PAY_EVERY_YEAR
|
||||
TransactionID string `json:"transaction_id,omitempty" bson:"transaction_id,omitempty"`
|
||||
WalletFrom string `json:"wallet_from,omitempty" bson:"wallet_from,omitempty"`
|
||||
WalletTo string `json:"wallet_to,omitempty" bson:"wallet_to,omitempty"`
|
||||
ScheduledAt *time.Time `json:"scheduled_at,omitempty" bson:"scheduled_at,omitempty"`
|
||||
ProcessedAt *time.Time `json:"processed_at,omitempty" bson:"processed_at,omitempty"`
|
||||
FailureReason string `json:"failure_reason,omitempty" bson:"failure_reason,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty" bson:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// NewInstantPayment crée un paiement immédiat (PAY_ONCE).
|
||||
func NewInstantPayment(billID, payerPeerID, recipientPeerID string, amount float64, currency string, method PaymentMethod) *Payment {
|
||||
return &Payment{
|
||||
BillID: billID,
|
||||
PayerPeerID: payerPeerID,
|
||||
RecipientPeerID: recipientPeerID,
|
||||
Amount: amount,
|
||||
Currency: currency,
|
||||
Status: PAYMENT_PENDING,
|
||||
Method: method,
|
||||
PaymentType: pricing.PAY_ONCE,
|
||||
}
|
||||
}
|
||||
|
||||
// NewScheduledPayment crée un paiement programmé (mensuel ou annuel).
|
||||
func NewScheduledPayment(subscriptionID, payerPeerID, recipientPeerID string, amount float64, currency string, method PaymentMethod, paymentType pricing.PaymentType, scheduledAt time.Time) *Payment {
|
||||
return &Payment{
|
||||
SubscriptionID: subscriptionID,
|
||||
PayerPeerID: payerPeerID,
|
||||
RecipientPeerID: recipientPeerID,
|
||||
Amount: amount,
|
||||
Currency: currency,
|
||||
Status: PAYMENT_PENDING,
|
||||
Method: method,
|
||||
PaymentType: paymentType,
|
||||
ScheduledAt: &scheduledAt,
|
||||
}
|
||||
}
|
||||
|
||||
// Complete marque le paiement comme confirmé.
|
||||
func (p *Payment) Complete(transactionID string) {
|
||||
now := time.Now().UTC()
|
||||
p.Status = PAYMENT_COMPLETED
|
||||
p.TransactionID = transactionID
|
||||
p.ProcessedAt = &now
|
||||
}
|
||||
|
||||
// Fail marque le paiement comme échoué.
|
||||
func (p *Payment) Fail(reason string) {
|
||||
now := time.Now().UTC()
|
||||
p.Status = PAYMENT_FAILED
|
||||
p.FailureReason = reason
|
||||
p.ProcessedAt = &now
|
||||
}
|
||||
|
||||
// Cancel annule le paiement s'il est encore en attente.
|
||||
func (p *Payment) Cancel() bool {
|
||||
if p.Status != PAYMENT_PENDING {
|
||||
return false
|
||||
}
|
||||
p.Status = PAYMENT_CANCELLED
|
||||
return true
|
||||
}
|
||||
|
||||
// IsRefundable indique si le paiement peut faire l'objet d'un remboursement.
|
||||
func (p *Payment) IsRefundable() bool {
|
||||
return p.Status == PAYMENT_COMPLETED
|
||||
}
|
||||
|
||||
func (p *Payment) GetAccessor(request *tools.APIRequest) utils.Accessor {
|
||||
return NewAccessor(request)
|
||||
}
|
||||
|
||||
func (p *Payment) StoreDraftDefault() {
|
||||
p.IsDraft = true
|
||||
}
|
||||
|
||||
func (p *Payment) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
|
||||
incoming := set.(*Payment)
|
||||
if !p.IsDraft && p.Status != incoming.Status {
|
||||
return true, &Payment{
|
||||
Status: incoming.Status,
|
||||
TransactionID: incoming.TransactionID,
|
||||
FailureReason: incoming.FailureReason,
|
||||
}
|
||||
}
|
||||
return p.IsDraft, set
|
||||
}
|
||||
|
||||
func (p *Payment) CanDelete() bool {
|
||||
return p.IsDraft || p.Status == PAYMENT_PENDING
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"cloud.o-forge.io/core/oc-lib/logs"
|
||||
"cloud.o-forge.io/core/oc-lib/models/utils"
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
)
|
||||
|
||||
type paymentMongoAccessor struct {
|
||||
utils.AbstractAccessor[*Payment]
|
||||
}
|
||||
|
||||
func NewAccessor(request *tools.APIRequest) *paymentMongoAccessor {
|
||||
return &paymentMongoAccessor{
|
||||
AbstractAccessor: utils.AbstractAccessor[*Payment]{
|
||||
Logger: logs.CreateLogger(tools.PAYMENT.String()),
|
||||
Request: request,
|
||||
Type: tools.PAYMENT,
|
||||
New: func() *Payment { return &Payment{} },
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user