use crate::error::Result;
use crate::protocol::core::{PaymentChallenge, PaymentCredential, Receipt};
use crate::protocol::intents::ChargeRequest;
use crate::protocol::traits::{ChargeMethod, VerificationError};
#[derive(Clone)]
pub struct Mpay<M> {
method: M,
realm: String,
secret_key: String,
}
impl<M> Mpay<M>
where
M: ChargeMethod,
{
pub fn new(method: M, realm: impl Into<String>, secret_key: impl Into<String>) -> Self {
Self {
method,
realm: realm.into(),
secret_key: secret_key.into(),
}
}
pub fn realm(&self) -> &str {
&self.realm
}
pub fn method_name(&self) -> &str {
self.method.method()
}
#[cfg(feature = "tempo")]
pub fn charge_challenge(
&self,
amount: &str,
currency: &str,
recipient: &str,
) -> Result<PaymentChallenge> {
crate::protocol::methods::tempo::charge_challenge(
&self.secret_key,
&self.realm,
amount,
currency,
recipient,
)
}
#[cfg(feature = "tempo")]
pub fn charge_challenge_with_options(
&self,
request: &ChargeRequest,
expires: Option<&str>,
description: Option<&str>,
) -> Result<PaymentChallenge> {
crate::protocol::methods::tempo::charge_challenge_with_options(
&self.secret_key,
&self.realm,
request,
expires,
description,
)
}
pub async fn verify(
&self,
credential: &PaymentCredential,
request: &ChargeRequest,
) -> std::result::Result<Receipt, VerificationError> {
#[cfg(feature = "tempo")]
{
let expected_id = crate::protocol::methods::tempo::generate_challenge_id(
&self.secret_key,
&self.realm,
credential.challenge.method.as_str(),
credential.challenge.intent.as_str(),
&credential.challenge.request,
credential.challenge.expires.as_deref(),
credential.challenge.digest.as_deref(),
);
if credential.challenge.id != expected_id {
return Err(VerificationError::new(
"Challenge ID mismatch - not issued by this server",
));
}
}
self.method.verify(credential, request).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::future::Future;
#[derive(Clone)]
struct MockMethod;
impl ChargeMethod for MockMethod {
fn method(&self) -> &str {
"mock"
}
fn verify(
&self,
_credential: &PaymentCredential,
_request: &ChargeRequest,
) -> impl Future<Output = std::result::Result<Receipt, VerificationError>> + Send {
async { Ok(Receipt::success("mock", "mock_ref")) }
}
}
#[test]
fn test_mpay_creation() {
let payment = Mpay::new(MockMethod, "api.example.com", "secret");
assert_eq!(payment.realm(), "api.example.com");
assert_eq!(payment.method_name(), "mock");
}
#[cfg(feature = "tempo")]
#[test]
fn test_charge_challenge_generation() {
let payment = Mpay::new(MockMethod, "api.example.com", "test-secret");
let challenge = payment
.charge_challenge(
"1000000",
"0x20c0000000000000000000000000000000000001",
"0x742d35Cc6634C0532925a3b844Bc9e7595f1B0F2",
)
.unwrap();
assert_eq!(challenge.realm, "api.example.com");
assert_eq!(challenge.method.as_str(), "tempo");
assert_eq!(challenge.intent.as_str(), "charge");
assert_eq!(challenge.id.len(), 43);
}
}