chore(labrinth): paypal webhook changes (#7157)

feat(labrinth): make paypal webhook de errors 500 instead of 400, make payout ID optional
This commit is contained in:
François-Xavier Talbot
2026-08-15 06:01:43 +00:00
committed by GitHub
parent 2070428080
commit b0ec93f8ab
+26 -5
View File
@@ -232,6 +232,13 @@ pub async fn paypal_webhook(
None, None,
) )
.await .await
// Make deserialization errors yield a 5xx instead of a 400. For
// webhooks, the server must respect the client's request schema rather
// than requiring the client to conform to the server's schema.
.map_err(|error| match error {
ApiError::Request(report) => ApiError::Internal(report),
error => error,
})
.wrap_api_err("verifying PayPal webhook signature")?; .wrap_api_err("verifying PayPal webhook signature")?;
if &webhook_res.verification_status != "SUCCESS" { if &webhook_res.verification_status != "SUCCESS" {
@@ -242,7 +249,7 @@ pub async fn paypal_webhook(
#[derive(Deserialize)] #[derive(Deserialize)]
struct PayPalResource { struct PayPalResource {
pub payout_item_id: String, pub payout_item_id: Option<String>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -252,7 +259,7 @@ pub async fn paypal_webhook(
} }
let webhook = serde_json::from_str::<PayPalWebhook>(&body) let webhook = serde_json::from_str::<PayPalWebhook>(&body)
.wrap_request_err("deserializing JSON data")?; .wrap_internal_err("deserializing PayPal webhook JSON data")?;
match &*webhook.event_type { match &*webhook.event_type {
"PAYMENT.PAYOUTS-ITEM.BLOCKED" "PAYMENT.PAYOUTS-ITEM.BLOCKED"
@@ -260,6 +267,13 @@ pub async fn paypal_webhook(
| "PAYMENT.PAYOUTS-ITEM.REFUNDED" | "PAYMENT.PAYOUTS-ITEM.REFUNDED"
| "PAYMENT.PAYOUTS-ITEM.RETURNED" | "PAYMENT.PAYOUTS-ITEM.RETURNED"
| "PAYMENT.PAYOUTS-ITEM.CANCELED" => { | "PAYMENT.PAYOUTS-ITEM.CANCELED" => {
let payout_item_id = webhook
.resource
.payout_item_id
.as_deref()
.wrap_internal_err(
"PayPal payout item webhook is missing `payout_item_id`",
)?;
let mut transaction = pool let mut transaction = pool
.begin() .begin()
.await .await
@@ -267,7 +281,7 @@ pub async fn paypal_webhook(
let result = sqlx::query!( let result = sqlx::query!(
"SELECT user_id, amount, fee FROM payouts WHERE platform_id = $1 AND status = $2", "SELECT user_id, amount, fee FROM payouts WHERE platform_id = $1 AND status = $2",
webhook.resource.payout_item_id, payout_item_id,
PayoutStatus::InTransit.as_str() PayoutStatus::InTransit.as_str()
) )
.fetch_optional(&mut transaction) .fetch_optional(&mut transaction)
@@ -286,7 +300,7 @@ pub async fn paypal_webhook(
PayoutStatus::Failed PayoutStatus::Failed
} }
.as_str(), .as_str(),
webhook.resource.payout_item_id payout_item_id
) )
.execute(&mut transaction) .execute(&mut transaction)
.await .await
@@ -309,6 +323,13 @@ pub async fn paypal_webhook(
} }
} }
"PAYMENT.PAYOUTS-ITEM.SUCCEEDED" => { "PAYMENT.PAYOUTS-ITEM.SUCCEEDED" => {
let payout_item_id = webhook
.resource
.payout_item_id
.as_deref()
.wrap_internal_err(
"PayPal payout item webhook is missing `payout_item_id`",
)?;
let mut transaction = pool let mut transaction = pool
.begin() .begin()
.await .await
@@ -320,7 +341,7 @@ pub async fn paypal_webhook(
WHERE platform_id = $2 WHERE platform_id = $2
", ",
PayoutStatus::Success.as_str(), PayoutStatus::Success.as_str(),
webhook.resource.payout_item_id payout_item_id
) )
.execute(&mut transaction) .execute(&mut transaction)
.await .await