Files
AstralRinth/packages/muralpay/src/error.rs
aecsocket 17f395ee55 Mural Pay integration (#4520)
* wip: muralpay integration

* Basic Mural Pay API bindings

* Fix clippy

* use dotenvy in muralpay example

* Refactor payout creation code

* wip: muralpay payout requests

* Mural Pay payouts work

* Fix clippy

* add mural pay fees API

* Work on payout fee API

* Fees API for more payment methods

* Fix CI

* Temporarily disable Venmo and PayPal methods from frontend

* wip: counterparties

* Start on counterparties and payment methods API

* Mural Pay multiple methods when fetching

* Don't send supported_countries to frontend

* Add countries to muralpay fiat methods

* Compile fix

* Add exchange rate info to fees endpoint

* Add fees to premium Tremendous options

* Add delivery email field to Tremendous payouts

* Add Tremendous product category to payout methods

* Add bank details API to muralpay

* Fix CI

* Fix CI

* Remove prepaid visa, compute fees properly for Tremendous methods

* Add more details to Tremendous errors

* Add fees to Mural

* Payout history route and bank details

* Re-add legacy PayPal/Venmo options for US

* move the mural bank details route

* Add utoipa support to payout endpoints

* address some PR comments

* add CORS to new utoipa routes

* Immediately approve mural payouts

* Add currency support to Tremendous payouts

* Currency forex

* add forex to tremendous fee request

* Add Mural balance to bank balance info

* Add more Tremendous currencies support

* Transaction payouts available use the correct date

* Address my own review comment

* Address PR comments

* Change Mural withdrawal limit to 3k

* maybe fix tremendous gift cards

* Change how Mural minimum withdrawals are calculated

* Tweak min/max withdrawal values

---------

Co-authored-by: Calum H. <contact@cal.engineer>
Co-authored-by: Alejandro González <me@alegon.dev>
2025-11-03 14:19:46 -08:00

118 lines
3.1 KiB
Rust

use std::{collections::HashMap, fmt};
use bytes::Bytes;
use derive_more::{Display, Error, From};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Display, Error, From)]
pub enum MuralError {
#[display("API error")]
Api(ApiError),
#[display("request error")]
Request(reqwest::Error),
#[display("failed to decode response\n{json:?}")]
#[from(skip)]
Decode {
source: serde_json::Error,
json: Bytes,
},
#[display("failed to decode error response\n{json:?}")]
#[from(skip)]
DecodeError {
source: serde_json::Error,
json: Bytes,
},
}
pub type Result<T, E = MuralError> = std::result::Result<T, E>;
#[derive(Debug, Display, Error, From)]
pub enum TransferError {
#[display("no transfer API key")]
NoTransferKey,
#[display("API error")]
Api(Box<ApiError>),
#[display("request error")]
Request(reqwest::Error),
#[display("failed to decode response\n{json:?}")]
#[from(skip)]
Decode {
source: serde_json::Error,
json: Bytes,
},
#[display("failed to decode error response\n{json:?}")]
#[from(skip)]
DecodeError {
source: serde_json::Error,
json: Bytes,
},
}
impl From<MuralError> for TransferError {
fn from(value: MuralError) -> Self {
match value {
MuralError::Api(x) => Self::Api(Box::new(x)),
MuralError::Request(x) => Self::Request(x),
MuralError::Decode { source, json } => {
Self::Decode { source, json }
}
MuralError::DecodeError { source, json } => {
Self::DecodeError { source, json }
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Error)]
#[serde(rename_all = "camelCase")]
pub struct ApiError {
pub error_instance_id: Uuid,
pub name: String,
pub message: String,
#[serde(deserialize_with = "one_or_many")]
#[serde(default)]
pub details: Vec<String>,
#[serde(default)]
pub params: HashMap<String, serde_json::Value>,
}
fn one_or_many<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum OneOrMany {
One(String),
Many(Vec<String>),
}
match OneOrMany::deserialize(deserializer)? {
OneOrMany::One(s) => Ok(vec![s]),
OneOrMany::Many(v) => Ok(v),
}
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut lines = vec![self.message.to_string()];
if !self.details.is_empty() {
lines.push("details:".into());
lines.extend(self.details.iter().map(|s| format!("- {s}")));
}
if !self.params.is_empty() {
lines.push("params:".into());
lines
.extend(self.params.iter().map(|(k, v)| format!("- {k}: {v}")));
}
lines.push(format!("error name: {}", self.name));
lines.push(format!("error instance id: {}", self.error_instance_id));
write!(f, "{}", lines.join("\n"))
}
}