1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
use super::{Error, SpspResponse};
use futures::{future::result, Future};
use interledger_packet::Address;
use interledger_service::{Account, IncomingService};
use interledger_stream::send_money;
use log::{debug, error, trace};
use reqwest::r#async::Client;
use std::convert::TryFrom;
pub fn query(server: &str) -> impl Future<Item = SpspResponse, Error = Error> {
let server = payment_pointer_to_url(server);
trace!("Querying receiver: {}", server);
let client = Client::new();
client
.get(&server)
.header("Accept", "application/spsp4+json")
.send()
.map_err(|err| Error::HttpError(format!("Error querying SPSP receiver: {:?}", err)))
.and_then(|mut res| {
res.json::<SpspResponse>()
.map_err(|err| Error::InvalidResponseError(format!("{:?}", err)))
})
}
pub fn pay<S, A>(
service: S,
from_account: A,
receiver: &str,
source_amount: u64,
) -> impl Future<Item = u64, Error = Error>
where
S: IncomingService<A> + Clone,
A: Account,
{
query(receiver).and_then(move |spsp| {
let shared_secret = spsp.shared_secret;
let dest = spsp.destination_account;
result(Address::try_from(dest).map_err(move |err| {
error!("Error parsing address");
Error::InvalidResponseError(err.to_string())
}))
.and_then(move |addr| {
debug!("Sending SPSP payment to address: {}", addr);
send_money(service, &from_account, addr, &shared_secret, source_amount)
.map(move |(amount_delivered, _plugin)| {
debug!(
"Sent SPSP payment of {} and delivered {} of the receiver's units",
source_amount, amount_delivered
);
amount_delivered
})
.map_err(move |err| {
error!("Error sending payment: {:?}", err);
Error::SendMoneyError(source_amount)
})
})
})
}
fn payment_pointer_to_url(payment_pointer: &str) -> String {
let mut url: String = if payment_pointer.starts_with('$') {
let mut url = "https://".to_string();
url.push_str(&payment_pointer[1..]);
url
} else {
payment_pointer.to_string()
};
let num_slashes = url.matches('/').count();
if num_slashes == 2 {
url.push_str("/.well-known/pay");
} else if num_slashes == 1 && url.ends_with('/') {
url.push_str(".well-known/pay");
}
trace!(
"Converted payment pointer: {} to URL: {}",
payment_pointer,
url
);
url
}
#[cfg(test)]
mod payment_pointer {
use super::*;
#[test]
fn converts_pointer() {
let pointer = "$subdomain.domain.example";
assert_eq!(
payment_pointer_to_url(pointer),
"https://subdomain.domain.example/.well-known/pay"
);
}
}