chore: add crypto payment
This commit is contained in:
@@ -162,6 +162,36 @@ func InitDB() *Database {
|
||||
log.Fatalf("❌ Erreur migration commandes.referral_used: %v", err)
|
||||
}
|
||||
|
||||
// Migration: méthode de paiement (cash par défaut, crypto si paiement NowPayments)
|
||||
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS payment_method VARCHAR(20) NOT NULL DEFAULT 'cash'`); err != nil {
|
||||
log.Fatalf("❌ Erreur migration commandes.payment_method: %v", err)
|
||||
}
|
||||
|
||||
// Migration: table de suivi des paiements crypto
|
||||
if _, err = database.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS crypto_payments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
command_id INTEGER NOT NULL REFERENCES commandes(id) ON DELETE CASCADE,
|
||||
nowpayment_id TEXT NOT NULL,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'waiting',
|
||||
price_amount NUMERIC(10,2) NOT NULL,
|
||||
price_currency VARCHAR(10) NOT NULL DEFAULT 'eur',
|
||||
pay_currency VARCHAR(20) NOT NULL,
|
||||
pay_address TEXT NOT NULL,
|
||||
pay_amount NUMERIC(20,8) DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`); err != nil {
|
||||
log.Fatalf("❌ Erreur migration crypto_payments: %v", err)
|
||||
}
|
||||
if _, err = database.Exec(`CREATE INDEX IF NOT EXISTS idx_crypto_payments_command_id ON crypto_payments(command_id)`); err != nil {
|
||||
log.Fatalf("❌ Erreur index crypto_payments.command_id: %v", err)
|
||||
}
|
||||
if _, err = database.Exec(`CREATE INDEX IF NOT EXISTS idx_crypto_payments_nowpayment_id ON crypto_payments(nowpayment_id)`); err != nil {
|
||||
log.Fatalf("❌ Erreur index crypto_payments.nowpayment_id: %v", err)
|
||||
}
|
||||
|
||||
// Migration: points extra pour tous les pools de points (stockage dynamique par clé)
|
||||
if _, err = database.Exec(`ALTER TABLE clients ADD COLUMN IF NOT EXISTS points_extra JSONB NOT NULL DEFAULT '{}'::jsonb`); err != nil {
|
||||
log.Fatalf("❌ Erreur migration clients.points_extra: %v", err)
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"gestion/models"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (d *Database) CreateCryptoPayment(commandID int, nowPaymentID, status, priceCurrency, payCurrency, payAddress string, priceAmount, payAmount float64) (*models.CryptoPayment, error) {
|
||||
var p models.CryptoPayment
|
||||
err := d.DB.QueryRow(`
|
||||
INSERT INTO crypto_payments (command_id, nowpayment_id, status, price_amount, price_currency, pay_currency, pay_address, pay_amount)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id, command_id, nowpayment_id, status, price_amount, price_currency, pay_currency, pay_address, pay_amount, created_at, updated_at`,
|
||||
commandID, nowPaymentID, status, priceAmount, priceCurrency, payCurrency, payAddress, payAmount,
|
||||
).Scan(&p.ID, &p.CommandID, &p.NowPaymentID, &p.Status, &p.PriceAmount, &p.PriceCurrency, &p.PayCurrency, &p.PayAddress, &p.PayAmount, &p.CreatedAt, &p.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CreateCryptoPayment: %w", err)
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func (d *Database) GetCryptoPaymentByCommandID(commandID int) (*models.CryptoPayment, error) {
|
||||
var p models.CryptoPayment
|
||||
err := d.DB.QueryRow(`
|
||||
SELECT id, command_id, nowpayment_id, status, price_amount, price_currency, pay_currency, pay_address, pay_amount, created_at, updated_at
|
||||
FROM crypto_payments WHERE command_id = $1
|
||||
ORDER BY created_at DESC LIMIT 1`, commandID,
|
||||
).Scan(&p.ID, &p.CommandID, &p.NowPaymentID, &p.Status, &p.PriceAmount, &p.PriceCurrency, &p.PayCurrency, &p.PayAddress, &p.PayAmount, &p.CreatedAt, &p.UpdatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func (d *Database) GetCryptoPaymentByNowPaymentID(nowPaymentID string) (*models.CryptoPayment, error) {
|
||||
var p models.CryptoPayment
|
||||
err := d.DB.QueryRow(`
|
||||
SELECT id, command_id, nowpayment_id, status, price_amount, price_currency, pay_currency, pay_address, pay_amount, created_at, updated_at
|
||||
FROM crypto_payments WHERE nowpayment_id = $1`, nowPaymentID,
|
||||
).Scan(&p.ID, &p.CommandID, &p.NowPaymentID, &p.Status, &p.PriceAmount, &p.PriceCurrency, &p.PayCurrency, &p.PayAddress, &p.PayAmount, &p.CreatedAt, &p.UpdatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func (d *Database) GetPendingCryptoPayments() ([]models.CryptoPayment, error) {
|
||||
rows, err := d.DB.Query(`
|
||||
SELECT id, command_id, nowpayment_id, status, price_amount, price_currency, pay_currency, pay_address, pay_amount, created_at, updated_at
|
||||
FROM crypto_payments
|
||||
WHERE status NOT IN ('finished', 'failed', 'expired', 'refunded')
|
||||
ORDER BY created_at ASC`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var payments []models.CryptoPayment
|
||||
for rows.Next() {
|
||||
var p models.CryptoPayment
|
||||
if err := rows.Scan(&p.ID, &p.CommandID, &p.NowPaymentID, &p.Status, &p.PriceAmount, &p.PriceCurrency, &p.PayCurrency, &p.PayAddress, &p.PayAmount, &p.CreatedAt, &p.UpdatedAt); err != nil {
|
||||
continue
|
||||
}
|
||||
payments = append(payments, p)
|
||||
}
|
||||
return payments, nil
|
||||
}
|
||||
|
||||
func (d *Database) UpdateCryptoPaymentStatus(id int, status string, payAmount float64) error {
|
||||
_, err := d.DB.Exec(`
|
||||
UPDATE crypto_payments SET status = $1, pay_amount = $2, updated_at = NOW()
|
||||
WHERE id = $3`, status, payAmount, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// ActivateCryptoCommand passe la commande de 'pending_payment' → 'pending' une fois le paiement confirmé
|
||||
func (d *Database) ActivateCryptoCommand(commandID int) error {
|
||||
_, err := d.DB.Exec(`
|
||||
UPDATE commandes SET status = 'pending', updated_at = NOW()
|
||||
WHERE id = $1 AND status = 'pending_payment'`, commandID)
|
||||
return err
|
||||
}
|
||||
|
||||
// CancelCryptoCommand annule une commande en attente de paiement et restaure le stock
|
||||
func (d *Database) CancelCryptoCommand(commandID int) error {
|
||||
tx, err := d.DB.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
rows, err := tx.Query(`SELECT product_id, quantite FROM command_items WHERE command_id = $1`, commandID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var productID int
|
||||
var qty float64
|
||||
if err := rows.Scan(&productID, &qty); err != nil {
|
||||
continue
|
||||
}
|
||||
if _, err := tx.Exec(`UPDATE products SET stock = stock + $1 WHERE id = $2`, qty, productID); err != nil {
|
||||
log.Printf("[CANCEL CRYPTO] erreur restauration stock produit %d: %v", productID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`UPDATE commandes SET status = 'cancelled', updated_at = NOW() WHERE id = $1 AND status = 'pending_payment'`, commandID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
@@ -72,7 +72,11 @@ type AppSettings struct {
|
||||
ShowAmendeScore bool `json:"show_amende_score"` // afficher le score d'amendes aux clients/cabine
|
||||
PointsEnabled bool `json:"points_enabled"` // afficher/activer le système de points
|
||||
PointsPools []PointsPool `json:"points_pools"` // types de points personnalisés
|
||||
ReferralEnabled bool `json:"referral_enabled"` // activer/désactiver le système de parrainage
|
||||
ReferralEnabled bool `json:"referral_enabled"` // activer/désactiver le système de parrainage
|
||||
CryptoPaymentEnabled bool `json:"crypto_payment_enabled"` // activer/désactiver le paiement crypto
|
||||
NowPaymentsAPIKey string `json:"nowpayments_api_key"` // clé API NowPayments
|
||||
NowPaymentsIPNSecret string `json:"nowpayments_ipn_secret"` // secret IPN NowPayments
|
||||
NowPaymentsCurrencies []string `json:"nowpayments_currencies"` // cryptos acceptées (ex: ["btc","eth","ltc"])
|
||||
DeliverySchedule DeliverySchedule `json:"delivery_schedule"` // horaires de livraison par jour
|
||||
PostalZones []PostalZone `json:"postal_zones"` // zones de livraison avec minimum de commande
|
||||
}
|
||||
@@ -151,6 +155,17 @@ func (d *Database) GetSettings() (AppSettings, error) {
|
||||
}
|
||||
case "referral_enabled":
|
||||
settings.ReferralEnabled = value == "true"
|
||||
case "crypto_payment_enabled":
|
||||
settings.CryptoPaymentEnabled = value == "true"
|
||||
case "nowpayments_api_key":
|
||||
settings.NowPaymentsAPIKey = value
|
||||
case "nowpayments_ipn_secret":
|
||||
settings.NowPaymentsIPNSecret = value
|
||||
case "nowpayments_currencies":
|
||||
var currencies []string
|
||||
if err := json.Unmarshal([]byte(value), ¤cies); err == nil {
|
||||
settings.NowPaymentsCurrencies = currencies
|
||||
}
|
||||
case "delivery_schedule":
|
||||
var sched DeliverySchedule
|
||||
if err := json.Unmarshal([]byte(value), &sched); err == nil {
|
||||
@@ -202,12 +217,24 @@ func (d *Database) UpdateSettings(s AppSettings) error {
|
||||
upsert := `INSERT INTO app_settings (key, value) VALUES ($1, $2)
|
||||
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`
|
||||
|
||||
if s.NowPaymentsCurrencies == nil {
|
||||
s.NowPaymentsCurrencies = []string{}
|
||||
}
|
||||
currenciesJSON, err := json.Marshal(s.NowPaymentsCurrencies)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur sérialisation nowpayments_currencies: %w", err)
|
||||
}
|
||||
|
||||
pairs := [][2]string{
|
||||
{"penalties_enabled", boolStr(s.PenaltiesEnabled)},
|
||||
{"show_amende_score", boolStr(s.ShowAmendeScore)},
|
||||
{"points_enabled", boolStr(s.PointsEnabled)},
|
||||
{"points_pools", string(poolsJSON)},
|
||||
{"referral_enabled", boolStr(s.ReferralEnabled)},
|
||||
{"crypto_payment_enabled", boolStr(s.CryptoPaymentEnabled)},
|
||||
{"nowpayments_api_key", s.NowPaymentsAPIKey},
|
||||
{"nowpayments_ipn_secret", s.NowPaymentsIPNSecret},
|
||||
{"nowpayments_currencies", string(currenciesJSON)},
|
||||
}
|
||||
|
||||
schedJSON, err := json.Marshal(s.DeliverySchedule)
|
||||
|
||||
Reference in New Issue
Block a user