Store method ID for payouts (#4752)

* Store method ID for payouts

* Fixes
This commit is contained in:
aecsocket
2025-11-10 08:41:06 -08:00
committed by GitHub
parent 9706f1597b
commit 98b4970680
6 changed files with 95 additions and 52 deletions

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "\n SELECT id, user_id, created, amount, status, method, method_address, platform_id, fee\n FROM payouts\n WHERE id = ANY($1)\n ", "query": "\n SELECT id, user_id, created, amount, status, method, method_id, method_address, platform_id, fee\n FROM payouts\n WHERE id = ANY($1)\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -35,16 +35,21 @@
}, },
{ {
"ordinal": 6, "ordinal": 6,
"name": "method_address", "name": "method_id",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 7, "ordinal": 7,
"name": "platform_id", "name": "method_address",
"type_info": "Text" "type_info": "Text"
}, },
{ {
"ordinal": 8, "ordinal": 8,
"name": "platform_id",
"type_info": "Text"
},
{
"ordinal": 9,
"name": "fee", "name": "fee",
"type_info": "Numeric" "type_info": "Numeric"
} }
@@ -63,8 +68,9 @@
true, true,
true, true,
true, true,
true,
true true
] ]
}, },
"hash": "83f8d3fcc4ba1544f593abaf29c79157bc35e3fca79cc93f6512ca01acd8e5ce" "hash": "2aa9704c9ead520fb61b4ca1e94c7c70a245b0cc48ae3f4883393bfd57a685f1"
} }

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "\n INSERT INTO payouts (\n id, amount, fee, user_id, status, method, method_address, platform_id\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8\n )\n ", "query": "\n INSERT INTO payouts (\n id, amount, fee, user_id, status, method, method_id, method_address, platform_id\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9\n )\n ",
"describe": { "describe": {
"columns": [], "columns": [],
"parameters": { "parameters": {
@@ -12,10 +12,11 @@
"Varchar", "Varchar",
"Text", "Text",
"Text", "Text",
"Text",
"Text" "Text"
] ]
}, },
"nullable": [] "nullable": []
}, },
"hash": "285c089b43bf0225ba03e279f7a227c3483bae818d077efdc54e588b858c8760" "hash": "c82bf13a2567f772a4c6eb3329971049791dab817ac07708873ac57986c17e2c"
} }

View File

@@ -0,0 +1,2 @@
ALTER TABLE payouts
ADD COLUMN method_id TEXT;

View File

@@ -15,7 +15,11 @@ pub struct DBPayout {
pub fee: Option<Decimal>, pub fee: Option<Decimal>,
pub method: Option<PayoutMethodType>, pub method: Option<PayoutMethodType>,
/// See [`crate::models::v3::payouts::Payout::method_id`].
pub method_id: Option<String>,
/// See [`crate::models::v3::payouts::Payout::method_address`].
pub method_address: Option<String>, pub method_address: Option<String>,
/// See [`crate::models::v3::payouts::Payout::platform_id`].
pub platform_id: Option<String>, pub platform_id: Option<String>,
} }
@@ -27,10 +31,10 @@ impl DBPayout {
sqlx::query!( sqlx::query!(
" "
INSERT INTO payouts ( INSERT INTO payouts (
id, amount, fee, user_id, status, method, method_address, platform_id id, amount, fee, user_id, status, method, method_id, method_address, platform_id
) )
VALUES ( VALUES (
$1, $2, $3, $4, $5, $6, $7, $8 $1, $2, $3, $4, $5, $6, $7, $8, $9
) )
", ",
self.id.0, self.id.0,
@@ -39,6 +43,7 @@ impl DBPayout {
self.user_id.0, self.user_id.0,
self.status.as_str(), self.status.as_str(),
self.method.as_ref().map(|x| x.as_str()), self.method.as_ref().map(|x| x.as_str()),
self.method_id,
self.method_address, self.method_address,
self.platform_id, self.platform_id,
) )
@@ -71,7 +76,7 @@ impl DBPayout {
let results = sqlx::query!( let results = sqlx::query!(
" "
SELECT id, user_id, created, amount, status, method, method_address, platform_id, fee SELECT id, user_id, created, amount, status, method, method_id, method_address, platform_id, fee
FROM payouts FROM payouts
WHERE id = ANY($1) WHERE id = ANY($1)
", ",
@@ -85,6 +90,7 @@ impl DBPayout {
status: PayoutStatus::from_string(&r.status), status: PayoutStatus::from_string(&r.status),
amount: r.amount, amount: r.amount,
method: r.method.and_then(|x| PayoutMethodType::from_string(&x)), method: r.method.and_then(|x| PayoutMethodType::from_string(&x)),
method_id: r.method_id,
method_address: r.method_address, method_address: r.method_address,
platform_id: r.platform_id, platform_id: r.platform_id,
fee: r.fee, fee: r.fee,

View File

@@ -18,8 +18,17 @@ pub struct Payout {
#[serde(with = "rust_decimal::serde::float_option")] #[serde(with = "rust_decimal::serde::float_option")]
pub fee: Option<Decimal>, pub fee: Option<Decimal>,
pub method: Option<PayoutMethodType>, pub method: Option<PayoutMethodType>,
/// the address this payout was sent to: ex: email, paypal email, venmo handle /// Platform-dependent identifier for the submethod.
///
/// See [`crate::routes::v3::payouts::TransactionItem::Withdrawal::method_id`].
pub method_id: Option<String>,
/// Address this payout was sent to: ex: email, paypal email, venmo handle.
pub method_address: Option<String>, pub method_address: Option<String>,
/// Platform-provided opaque identifier for the transaction linked to this payout.
///
/// - Tremendous: reward ID
/// - Mural: payout request UUID
/// - PayPal/Venmo: transaction ID
pub platform_id: Option<String>, pub platform_id: Option<String>,
} }
@@ -33,6 +42,7 @@ impl Payout {
amount: data.amount, amount: data.amount,
fee: data.fee, fee: data.fee,
method: data.method, method: data.method,
method_id: data.method_id,
method_address: data.method_address, method_address: data.method_address,
platform_id: data.platform_id, platform_id: data.platform_id,
} }

View File

@@ -10,6 +10,7 @@ use crate::models::payouts::{
MuralPayDetails, PayoutMethodRequest, PayoutMethodType, PayoutStatus, MuralPayDetails, PayoutMethodRequest, PayoutMethodType, PayoutStatus,
TremendousDetails, TremendousForexResponse, TremendousDetails, TremendousForexResponse,
}; };
use crate::queue::payouts::mural::MuralPayoutRequest;
use crate::queue::payouts::{PayoutFees, PayoutsQueue}; use crate::queue::payouts::{PayoutFees, PayoutsQueue};
use crate::queue::session::AuthQueue; use crate::queue::session::AuthQueue;
use crate::routes::ApiError; use crate::routes::ApiError;
@@ -772,6 +773,7 @@ async fn tremendous_payout(
amount: amount_minus_fee, amount: amount_minus_fee,
fee: Some(total_fee), fee: Some(total_fee),
method: Some(PayoutMethodType::Tremendous), method: Some(PayoutMethodType::Tremendous),
method_id: Some(body.method_id.clone()),
method_address: Some(user_email.to_string()), method_address: Some(user_email.to_string()),
platform_id, platform_id,
}) })
@@ -806,6 +808,16 @@ async fn mural_pay_payout(
) )
.await?; .await?;
let method_id = match &details.payout_details {
MuralPayoutRequest::Blockchain { .. } => {
"blockchain-usdc-polygon".to_string()
}
MuralPayoutRequest::Fiat {
fiat_and_rail_details,
..
} => fiat_and_rail_details.code().to_string(),
};
Ok(DBPayout { Ok(DBPayout {
id: payout_id, id: payout_id,
user_id: user.id, user_id: user.id,
@@ -814,6 +826,7 @@ async fn mural_pay_payout(
amount: amount_minus_fee, amount: amount_minus_fee,
fee: Some(total_fee), fee: Some(total_fee),
method: Some(PayoutMethodType::MuralPay), method: Some(PayoutMethodType::MuralPay),
method_id: Some(method_id),
method_address: Some(user_email.to_string()), method_address: Some(user_email.to_string()),
platform_id: Some(payout_request.id.to_string()), platform_id: Some(payout_request.id.to_string()),
}) })
@@ -883,18 +896,6 @@ async fn paypal_payout(
pub links: Vec<PayPalLink>, pub links: Vec<PayPalLink>,
} }
let mut payout_item = crate::database::models::payout_item::DBPayout {
id: payout_id,
user_id: user.id,
created: Utc::now(),
status: PayoutStatus::InTransit,
amount: amount_minus_fee,
fee: Some(total_fee),
method: Some(body.method.method_type()),
method_address: Some(display_address.clone()),
platform_id: None,
};
let res: PayoutsResponse = payouts_queue.make_paypal_request( let res: PayoutsResponse = payouts_queue.make_paypal_request(
Method::POST, Method::POST,
"payments/payouts", "payments/payouts",
@@ -922,33 +923,50 @@ async fn paypal_payout(
None None
).await?; ).await?;
if let Some(link) = res.links.first() { let link = res
#[derive(Deserialize)] .links
struct PayoutItem { .first()
pub payout_item_id: String, .wrap_request_err("no PayPal links available")?;
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct PayoutData { struct PayoutItem {
pub items: Vec<PayoutItem>, pub payout_item_id: String,
}
if let Ok(res) = payouts_queue
.make_paypal_request::<(), PayoutData>(
Method::GET,
&link.href,
None,
None,
Some(true),
)
.await
&& let Some(data) = res.items.first()
{
payout_item.platform_id = Some(data.payout_item_id.clone());
}
} }
Ok(payout_item) #[derive(Deserialize)]
struct PayoutData {
pub items: Vec<PayoutItem>,
}
let res = payouts_queue
.make_paypal_request::<(), PayoutData>(
Method::GET,
&link.href,
None,
None,
Some(true),
)
.await
.wrap_internal_err("failed to make PayPal request")?;
let data = res
.items
.first()
.wrap_internal_err("no payout items returned from PayPal request")?;
let platform_id = Some(data.payout_item_id.clone());
Ok(DBPayout {
id: payout_id,
user_id: user.id,
created: Utc::now(),
status: PayoutStatus::InTransit,
amount: amount_minus_fee,
fee: Some(total_fee),
method: Some(body.method.method_type()),
method_id: Some(body.method_id.clone()),
method_address: Some(display_address.clone()),
platform_id,
})
} }
/// User performing a payout-related action. /// User performing a payout-related action.
@@ -972,9 +990,11 @@ pub enum TransactionItem {
/// Payout-method-specific ID for the type of payout the user got. /// Payout-method-specific ID for the type of payout the user got.
/// ///
/// - Tremendous: the rewarded gift card ID. /// - Tremendous: the rewarded gift card ID.
/// - Mural: the payment rail used, i.e. crypto USDC or fiat USD. /// - Mural: the payment rail code used.
/// - PayPal: `paypal_us` /// - Blockchain: `blockchain-usdc-polygon`.
/// - Venmo: `venmo` /// - Fiat: see [`muralpay::FiatAndRailCode`].
/// - PayPal: `paypal_us`.
/// - Venmo: `venmo`.
/// ///
/// For legacy transactions, this may be [`None`] as we did not always /// For legacy transactions, this may be [`None`] as we did not always
/// store this payout info. /// store this payout info.
@@ -1061,9 +1081,7 @@ pub async fn transaction_history(
amount: payout.amount, amount: payout.amount,
fee: payout.fee, fee: payout.fee,
method_type: payout.method, method_type: payout.method,
// TODO: store the `method_id` in the database, and return it here method_id: payout.method_id,
// don't use the `platform_id`, that's something else
method_id: None,
method_address: payout.method_address, method_address: payout.method_address,
}); });