220 lines
5.5 KiB
Rust
220 lines
5.5 KiB
Rust
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::{FromRow, PgPool};
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
|
pub struct Wallet {
|
|
pub id: Uuid,
|
|
pub user_id: Uuid,
|
|
pub balance: i32,
|
|
pub reserved: i32,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
|
pub struct LedgerEntry {
|
|
pub id: Uuid,
|
|
pub wallet_id: Uuid,
|
|
pub transaction_type: String,
|
|
pub amount: i32,
|
|
pub reference_type: String,
|
|
pub reference_id: Option<Uuid>,
|
|
pub balance_after: Option<i32>,
|
|
pub remarks: Option<String>,
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
pub struct TracecoinWalletRepository;
|
|
|
|
impl TracecoinWalletRepository {
|
|
pub async fn get_by_user_id(pool: &PgPool, user_id: Uuid) -> Result<Wallet, sqlx::Error> {
|
|
sqlx::query_as::<_, Wallet>(
|
|
"SELECT * FROM tracecoin_wallets WHERE user_id = $1",
|
|
)
|
|
.bind(user_id)
|
|
.fetch_one(pool)
|
|
.await
|
|
}
|
|
|
|
pub async fn ensure_wallet(pool: &PgPool, user_id: Uuid) -> Result<(), sqlx::Error> {
|
|
sqlx::query(
|
|
r#"
|
|
INSERT INTO tracecoin_wallets (user_id, balance, reserved)
|
|
VALUES ($1, 0, 0)
|
|
ON CONFLICT (user_id) DO NOTHING
|
|
"#,
|
|
)
|
|
.bind(user_id)
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn try_reserve_tracecoins(
|
|
pool: &PgPool,
|
|
user_id: Uuid,
|
|
amount: i32,
|
|
reference_id: Uuid,
|
|
) -> Result<bool, sqlx::Error> {
|
|
let mut tx = pool.begin().await?;
|
|
|
|
sqlx::query(
|
|
r#"
|
|
INSERT INTO tracecoin_wallets (user_id, balance, reserved)
|
|
VALUES ($1, 0, 0)
|
|
ON CONFLICT (user_id) DO NOTHING
|
|
"#,
|
|
)
|
|
.bind(user_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
let wallet = sqlx::query_as::<_, Wallet>(
|
|
"SELECT * FROM tracecoin_wallets WHERE user_id = $1 FOR UPDATE",
|
|
)
|
|
.bind(user_id)
|
|
.fetch_one(&mut *tx)
|
|
.await?;
|
|
|
|
if wallet.balance < amount {
|
|
tx.rollback().await?;
|
|
return Ok(false);
|
|
}
|
|
|
|
sqlx::query(
|
|
r#"
|
|
UPDATE tracecoin_wallets
|
|
SET balance = balance - $1, reserved = reserved + $1, updated_at = NOW()
|
|
WHERE id = $2
|
|
"#,
|
|
)
|
|
.bind(amount)
|
|
.bind(wallet.id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
sqlx::query(
|
|
r#"
|
|
INSERT INTO tracecoin_ledger (wallet_id, transaction_type, amount, reference_type, reference_id)
|
|
VALUES ($1, 'RESERVE', $2, 'LEAD_REQUEST', $3)
|
|
"#,
|
|
)
|
|
.bind(wallet.id)
|
|
.bind(amount)
|
|
.bind(reference_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
tx.commit().await?;
|
|
Ok(true)
|
|
}
|
|
|
|
pub async fn try_debit_reserved_tracecoins(
|
|
pool: &PgPool,
|
|
user_id: Uuid,
|
|
amount: i32,
|
|
reference_id: Uuid,
|
|
) -> Result<bool, sqlx::Error> {
|
|
let mut tx = pool.begin().await?;
|
|
|
|
let wallet = sqlx::query_as::<_, Wallet>(
|
|
"SELECT * FROM tracecoin_wallets WHERE user_id = $1 FOR UPDATE",
|
|
)
|
|
.bind(user_id)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
|
|
let Some(wallet) = wallet else {
|
|
tx.rollback().await?;
|
|
return Ok(false);
|
|
};
|
|
|
|
if wallet.reserved < amount {
|
|
tx.rollback().await?;
|
|
return Ok(false);
|
|
}
|
|
|
|
sqlx::query(
|
|
r#"
|
|
UPDATE tracecoin_wallets
|
|
SET reserved = reserved - $1, updated_at = NOW()
|
|
WHERE id = $2
|
|
"#,
|
|
)
|
|
.bind(amount)
|
|
.bind(wallet.id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
sqlx::query(
|
|
r#"
|
|
INSERT INTO tracecoin_ledger (wallet_id, transaction_type, amount, reference_type, reference_id)
|
|
VALUES ($1, 'DEBIT', $2, 'LEAD_ACCEPTED', $3)
|
|
"#,
|
|
)
|
|
.bind(wallet.id)
|
|
.bind(amount)
|
|
.bind(reference_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
tx.commit().await?;
|
|
Ok(true)
|
|
}
|
|
|
|
pub async fn try_release_reserved_tracecoins(
|
|
pool: &PgPool,
|
|
user_id: Uuid,
|
|
amount: i32,
|
|
reference_id: Uuid,
|
|
reason: &str,
|
|
) -> Result<bool, sqlx::Error> {
|
|
let mut tx = pool.begin().await?;
|
|
|
|
let wallet = sqlx::query_as::<_, Wallet>(
|
|
"SELECT * FROM tracecoin_wallets WHERE user_id = $1 FOR UPDATE",
|
|
)
|
|
.bind(user_id)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
|
|
let Some(wallet) = wallet else {
|
|
tx.rollback().await?;
|
|
return Ok(false);
|
|
};
|
|
|
|
if wallet.reserved < amount {
|
|
tx.rollback().await?;
|
|
return Ok(false);
|
|
}
|
|
|
|
sqlx::query(
|
|
r#"
|
|
UPDATE tracecoin_wallets
|
|
SET reserved = reserved - $1, balance = balance + $1, updated_at = NOW()
|
|
WHERE id = $2
|
|
"#,
|
|
)
|
|
.bind(amount)
|
|
.bind(wallet.id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
sqlx::query(
|
|
r#"
|
|
INSERT INTO tracecoin_ledger (wallet_id, transaction_type, amount, reference_type, reference_id)
|
|
VALUES ($1, 'RELEASE', $2, $3, $4)
|
|
"#,
|
|
)
|
|
.bind(wallet.id)
|
|
.bind(amount)
|
|
.bind(reason)
|
|
.bind(reference_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
tx.commit().await?;
|
|
Ok(true)
|
|
}
|
|
}
|