use serde::de::{DeserializeOwned, Error};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::sync::Arc;
use crate::chain::ChainId;
use crate::proto;
use crate::proto::v1;
use crate::proto::{OriginalJson, SupportedResponse};
use crate::scheme::ExtensionKey;
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct X402Version2;
impl X402Version2 {
pub const VALUE: u8 = 2;
}
impl PartialEq<u8> for X402Version2 {
fn eq(&self, other: &u8) -> bool {
*other == Self::VALUE
}
}
impl From<X402Version2> for u8 {
fn from(_: X402Version2) -> Self {
X402Version2::VALUE
}
}
impl Serialize for X402Version2 {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_u8(Self::VALUE)
}
}
impl<'de> Deserialize<'de> for X402Version2 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let num = u8::deserialize(deserializer)?;
if num == Self::VALUE {
Ok(X402Version2)
} else {
Err(serde::de::Error::custom(format!(
"expected version {}, got {}",
Self::VALUE,
num
)))
}
}
}
impl Display for X402Version2 {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", Self::VALUE)
}
}
pub type VerifyResponse = v1::VerifyResponse;
pub type SettleResponse = v1::SettleResponse;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourceInfo {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VerifyRequest<TPayload, TRequirements> {
pub x402_version: X402Version2,
pub payment_payload: TPayload,
pub payment_requirements: TRequirements,
}
impl<TPayload, TRequirements> TryFrom<&VerifyRequest<TPayload, TRequirements>>
for proto::VerifyRequest
where
TPayload: Serialize,
TRequirements: Serialize,
{
type Error = serde_json::Error;
fn try_from(value: &VerifyRequest<TPayload, TRequirements>) -> Result<Self, Self::Error> {
let json = serde_json::to_string(value)?;
let raw = serde_json::value::RawValue::from_string(json)?;
Ok(Self(raw))
}
}
impl<TPayload, TRequirements> TryFrom<&proto::VerifyRequest>
for VerifyRequest<TPayload, TRequirements>
where
Self: DeserializeOwned,
{
type Error = proto::PaymentVerificationError;
fn try_from(value: &proto::VerifyRequest) -> Result<Self, Self::Error> {
let value = serde_json::from_str(value.as_str())?;
Ok(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentPayload<TPaymentRequirements, TPayload> {
pub accepted: TPaymentRequirements,
pub payload: TPayload,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource: Option<ResourceInfo>,
pub x402_version: X402Version2,
#[serde(default, skip_serializing_if = "ExtensionsJson::is_empty")]
pub extensions: ExtensionsJson,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ExtensionsJson(serde_json::value::Map<String, serde_json::Value>);
impl ExtensionsJson {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn insert<T>(&mut self, value: T) -> serde_json::Result<Option<serde_json::Value>>
where
T: ExtensionKey + Serialize,
{
let key = T::EXTENSION_KEY.to_string();
let value = serde_json::to_value(value)?;
Ok(self.0.insert(key, value))
}
pub fn get<T>(&self) -> Option<T>
where
T: ExtensionKey + DeserializeOwned,
{
self.0
.get(T::EXTENSION_KEY)
.and_then(|v| T::deserialize(v).ok())
}
}
impl FromIterator<(String, serde_json::Value)> for ExtensionsJson {
fn from_iter<I: IntoIterator<Item = (String, serde_json::Value)>>(iter: I) -> Self {
ExtensionsJson(iter.into_iter().collect())
}
}
impl From<ExtensionsJson> for serde_json::Value {
fn from(value: ExtensionsJson) -> Self {
serde_json::Value::Object(value.0)
}
}
impl AsRef<serde_json::Map<String, serde_json::Value>> for ExtensionsJson {
fn as_ref(&self) -> &serde_json::Map<String, serde_json::Value> {
&self.0
}
}
impl TryFrom<serde_json::Value> for ExtensionsJson {
type Error = serde_json::Error;
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
if let serde_json::Value::Object(map) = value {
Ok(ExtensionsJson(map))
} else {
Err(serde_json::Error::custom("expected object"))
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PaymentRequirements<
TScheme = String,
TAmount = String,
TAddress = String,
TExtra = Option<serde_json::Value>,
> {
pub scheme: TScheme,
pub network: ChainId,
pub amount: TAmount,
pub pay_to: TAddress,
pub max_timeout_seconds: u64,
pub asset: TAddress,
pub extra: 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: X402Version2,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub resource: Option<ResourceInfo>,
#[serde(default = "Vec::default")]
pub accepts: Vec<TAccepts>,
#[serde(default, skip_serializing_if = "ExtensionsJson::is_empty")]
pub extensions: ExtensionsJson,
}
#[derive(Clone)]
#[allow(dead_code)] pub struct PriceTag {
pub requirements: PaymentRequirements,
#[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("requirements", &self.requirements)
.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.requirements.max_timeout_seconds = seconds;
self
}
}
impl PartialEq<PaymentRequirements> for PriceTag {
fn eq(&self, b: &PaymentRequirements) -> bool {
let a = &self.requirements;
a == b
}
}