use crate::error::MppError;
use crate::protocol::core::{PaymentChallenge, PaymentCredential};
use std::future::Future;
pub trait PaymentProvider: Clone + Send + Sync {
fn supports(&self, method: &str, intent: &str) -> bool;
fn pay(
&self,
challenge: &PaymentChallenge,
) -> impl Future<Output = Result<PaymentCredential, MppError>> + Send;
}
#[derive(Clone)]
pub struct MultiProvider {
providers: Vec<Box<dyn DynPaymentProvider>>,
}
impl MultiProvider {
pub fn new() -> Self {
Self {
providers: Vec::new(),
}
}
pub fn with<P: PaymentProvider + 'static>(mut self, provider: P) -> Self {
self.providers.push(Box::new(provider));
self
}
pub fn add<P: PaymentProvider + 'static>(&mut self, provider: P) -> &mut Self {
self.providers.push(Box::new(provider));
self
}
pub fn has_support(&self, method: &str, intent: &str) -> bool {
self.providers
.iter()
.any(|p| p.dyn_supports(method, intent))
}
}
impl Default for MultiProvider {
fn default() -> Self {
Self::new()
}
}
impl PaymentProvider for MultiProvider {
fn supports(&self, method: &str, intent: &str) -> bool {
self.has_support(method, intent)
}
async fn pay(&self, challenge: &PaymentChallenge) -> Result<PaymentCredential, MppError> {
let method = challenge.method.as_str();
let intent = challenge.intent.as_str();
for provider in &self.providers {
if provider.dyn_supports(method, intent) {
return provider.dyn_pay(challenge).await;
}
}
Err(MppError::UnsupportedPaymentMethod(format!(
"no provider supports method={}, intent={}",
method, intent
)))
}
}
trait DynPaymentProvider: Send + Sync {
fn dyn_supports(&self, method: &str, intent: &str) -> bool;
fn dyn_pay<'a>(
&'a self,
challenge: &'a PaymentChallenge,
) -> std::pin::Pin<Box<dyn Future<Output = Result<PaymentCredential, MppError>> + Send + 'a>>;
fn clone_box(&self) -> Box<dyn DynPaymentProvider>;
}
impl<P: PaymentProvider + 'static> DynPaymentProvider for P {
fn dyn_supports(&self, method: &str, intent: &str) -> bool {
PaymentProvider::supports(self, method, intent)
}
fn dyn_pay<'a>(
&'a self,
challenge: &'a PaymentChallenge,
) -> std::pin::Pin<Box<dyn Future<Output = Result<PaymentCredential, MppError>> + Send + 'a>>
{
Box::pin(PaymentProvider::pay(self, challenge))
}
fn clone_box(&self) -> Box<dyn DynPaymentProvider> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn DynPaymentProvider> {
fn clone(&self) -> Self {
self.clone_box()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct MockProvider {
method: &'static str,
intent: &'static str,
}
impl PaymentProvider for MockProvider {
fn supports(&self, method: &str, intent: &str) -> bool {
self.method == method && self.intent == intent
}
async fn pay(&self, challenge: &PaymentChallenge) -> Result<PaymentCredential, MppError> {
use crate::protocol::core::PaymentPayload;
Ok(PaymentCredential::new(
challenge.to_echo(),
PaymentPayload::hash(format!("mock-{}", self.method)),
))
}
}
#[test]
fn test_multi_provider_supports() {
let multi = MultiProvider::new()
.with(MockProvider {
method: "tempo",
intent: "charge",
})
.with(MockProvider {
method: "stripe",
intent: "charge",
});
assert!(multi.has_support("tempo", "charge"));
assert!(multi.has_support("stripe", "charge"));
assert!(!multi.has_support("bitcoin", "charge"));
assert!(!multi.has_support("tempo", "authorize"));
}
#[test]
fn test_multi_provider_empty() {
let multi = MultiProvider::new();
assert!(!multi.has_support("tempo", "charge"));
}
#[test]
fn test_multi_provider_clone() {
let multi = MultiProvider::new().with(MockProvider {
method: "tempo",
intent: "charge",
});
let cloned = multi.clone();
assert!(cloned.has_support("tempo", "charge"));
}
}