use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::fmt::Display;
use std::sync::Arc;
use crate::proto;
use crate::proto::{OriginalJson, SupportedResponse};
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct X402Version1;
impl X402Version1 {
pub const VALUE: u8 = 1;
}
impl PartialEq<u8> for X402Version1 {
fn eq(&self, other: &u8) -> bool {
*other == Self::VALUE
}
}
impl From<X402Version1> for u8 {
fn from(_: X402Version1) -> Self {
X402Version1::VALUE
}
}
impl Serialize for X402Version1 {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_u8(Self::VALUE)
}
}
impl<'de> Deserialize<'de> for X402Version1 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let num = u8::deserialize(deserializer)?;
if num == Self::VALUE {
Ok(X402Version1)
} else {
Err(serde::de::Error::custom(format!(
"expected version {}, got {}",
Self::VALUE,
num
)))
}
}
}
impl Display for X402Version1 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", Self::VALUE)
}
}
#[derive(Debug, Clone)]
pub enum SettleResponse {
Success {
payer: String,
transaction: String,
network: String,
},
Error {
reason: String,
network: String,
},
}
impl From<SettleResponse> for proto::SettleResponse {
fn from(val: SettleResponse) -> Self {
proto::SettleResponse(
serde_json::to_value(val).expect("SettleResponse serialization failed"),
)
}
}
#[derive(Serialize, Deserialize)]
struct SettleResponseWire {
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payer: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction: Option<String>,
pub network: String,
}
impl Serialize for SettleResponse {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let wire = match self {
SettleResponse::Success {
payer,
transaction,
network,
} => SettleResponseWire {
success: true,
error_reason: None,
payer: Some(payer.clone()),
transaction: Some(transaction.clone()),
network: network.clone(),
},
SettleResponse::Error { reason, network } => SettleResponseWire {
success: false,
error_reason: Some(reason.clone()),
payer: None,
transaction: None,
network: network.clone(),
},
};
wire.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for SettleResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let wire = SettleResponseWire::deserialize(deserializer)?;
match wire.success {
true => {
let payer = wire
.payer
.ok_or_else(|| serde::de::Error::missing_field("payer"))?;
let transaction = wire
.transaction
.ok_or_else(|| serde::de::Error::missing_field("transaction"))?;
Ok(SettleResponse::Success {
payer,
transaction,
network: wire.network,
})
}
false => {
let reason = wire
.error_reason
.ok_or_else(|| serde::de::Error::missing_field("error_reason"))?;
Ok(SettleResponse::Error {
reason,
network: wire.network,
})
}
}
}
}
#[derive(Debug)]
pub enum VerifyResponse {
Valid { payer: String },
Invalid {
reason: String,
payer: Option<String>,
},
}
impl From<VerifyResponse> for proto::VerifyResponse {
fn from(val: VerifyResponse) -> Self {
proto::VerifyResponse(
serde_json::to_value(val).expect("VerifyResponse serialization failed"),
)
}
}
impl TryFrom<proto::VerifyResponse> for VerifyResponse {
type Error = serde_json::Error;
fn try_from(value: proto::VerifyResponse) -> Result<Self, Self::Error> {
let json = value.0;
serde_json::from_value(json)
}
}
impl VerifyResponse {
pub fn valid(payer: String) -> Self {
VerifyResponse::Valid { payer }
}
#[allow(dead_code)]
pub fn invalid(payer: Option<String>, reason: String) -> Self {
VerifyResponse::Invalid { reason, payer }
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct VerifyResponseWire {
is_valid: bool,
#[serde(skip_serializing_if = "Option::is_none")]
payer: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
invalid_reason: Option<String>,
}
impl Serialize for VerifyResponse {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let wire = match self {
VerifyResponse::Valid { payer } => VerifyResponseWire {
is_valid: true,
payer: Some(payer.clone()),
invalid_reason: None,
},
VerifyResponse::Invalid { reason, payer } => VerifyResponseWire {
is_valid: false,
payer: payer.clone(),
invalid_reason: Some(reason.clone()),
},
};
wire.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for VerifyResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let wire = VerifyResponseWire::deserialize(deserializer)?;
match wire.is_valid {
true => {
let payer = wire
.payer
.ok_or_else(|| serde::de::Error::missing_field("payer"))?;
Ok(VerifyResponse::Valid { payer })
}
false => {
let reason = wire
.invalid_reason
.ok_or_else(|| serde::de::Error::missing_field("invalid_reason"))?;
let payer = wire.payer;
Ok(VerifyResponse::Invalid { reason, payer })
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VerifyRequest<TPayload, TRequirements> {
pub x402_version: X402Version1,
pub payment_payload: TPayload,
pub payment_requirements: TRequirements,
}
impl<TPayload, TRequirements> VerifyRequest<TPayload, TRequirements>
where
Self: DeserializeOwned,
{
pub fn from_proto(
request: proto::VerifyRequest,
) -> Result<Self, proto::PaymentVerificationError> {
let value = serde_json::from_str(request.as_str())?;
Ok(value)
}
}
impl<TPayload, TRequirements> TryInto<proto::VerifyRequest>
for VerifyRequest<TPayload, TRequirements>
where
TPayload: Serialize,
TRequirements: Serialize,
{
type Error = serde_json::Error;
fn try_into(self) -> Result<proto::VerifyRequest, Self::Error> {
let json = serde_json::to_value(self)?;
let raw = serde_json::value::to_raw_value(&json)?;
Ok(proto::VerifyRequest(raw))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentPayload<TScheme = String, TPayload = Box<serde_json::value::RawValue>> {
pub x402_version: X402Version1,
pub scheme: TScheme,
pub network: String,
pub payload: TPayload,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentRequirements<
TScheme = String,
TAmount = String,
TAddress = String,
TExtra = serde_json::Value,
> {
pub scheme: TScheme,
pub network: String,
pub max_amount_required: TAmount,
pub resource: String,
pub description: String,
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_schema: Option<serde_json::Value>,
pub pay_to: TAddress,
pub max_timeout_seconds: u64,
pub asset: TAddress,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<TExtra>,
}
impl<TScheme, TAmount, TAddress, TExtra> TryFrom<&OriginalJson>
for PaymentRequirements<TScheme, TAmount, TAddress, TExtra>
where
TScheme: for<'a> serde::Deserialize<'a>,
TAmount: for<'a> serde::Deserialize<'a>,
TAddress: for<'a> serde::Deserialize<'a>,
TExtra: for<'a> serde::Deserialize<'a>,
{
type Error = serde_json::Error;
fn try_from(value: &OriginalJson) -> Result<Self, Self::Error> {
let payment_requirements = serde_json::from_str(value.0.get())?;
Ok(payment_requirements)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentRequired<TAccepts = PaymentRequirements> {
pub x402_version: X402Version1,
#[serde(default = "Vec::default")]
pub accepts: Vec<TAccepts>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Clone)]
#[allow(dead_code)] pub struct PriceTag {
pub scheme: String,
pub pay_to: String,
pub asset: String,
pub network: String,
pub amount: String,
pub max_timeout_seconds: u64,
pub extra: Option<serde_json::Value>,
#[doc(hidden)]
pub enricher: Option<Enricher>,
}
impl fmt::Debug for PriceTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PriceTag")
.field("scheme", &self.scheme)
.field("pay_to", &self.pay_to)
.field("asset", &self.asset)
.field("network", &self.network)
.field("amount", &self.amount)
.field("max_timeout_seconds", &self.max_timeout_seconds)
.field("extra", &self.extra)
.finish()
}
}
pub type Enricher = Arc<dyn Fn(&mut PriceTag, &SupportedResponse) + Send + Sync>;
impl PriceTag {
#[allow(dead_code)]
pub fn enrich(&mut self, capabilities: &SupportedResponse) {
if let Some(enricher) = self.enricher.clone() {
enricher(self, capabilities);
}
}
#[allow(dead_code)]
pub fn with_timeout(mut self, seconds: u64) -> Self {
self.max_timeout_seconds = seconds;
self
}
}