use crate::{Client, Hash};
use rust_decimal::Decimal;
use secrecy::Secret;
use serde::de::{self, Unexpected, Visitor};
use serde::{Deserialize, Serialize};
use std::fmt;
use time::{format_description, Date};
use url::Url;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Update {
reference: String,
#[serde(rename = "paynowreference")]
paynow_reference: u64,
amount: Decimal,
status: Status,
#[serde(rename = "pollurl")]
poll_url: Url,
#[serde(flatten)]
token: Option<Token>,
hash: Secret<Hash>,
}
impl Update {
#[must_use]
pub fn reference(&self) -> &str {
&self.reference
}
#[must_use]
pub fn take_reference(self) -> String {
self.reference
}
#[must_use]
pub fn paynow_reference(&self) -> u64 {
self.paynow_reference
}
#[must_use]
pub fn amount(&self) -> Decimal {
self.amount
}
#[must_use]
pub fn status(&self) -> Status {
self.status
}
#[must_use]
pub fn poll_url(&self) -> &Url {
&self.poll_url
}
#[must_use]
pub fn take_poll_url(self) -> Url {
self.poll_url
}
#[must_use]
pub fn token(&self) -> Option<&Token> {
self.token.as_ref()
}
#[must_use]
pub fn take_token(self) -> Option<Token> {
self.token
}
#[allow(clippy::missing_panics_doc)]
pub fn validate(&self, client: &Client) -> Result<(), crate::Error> {
let format = format_description::parse("[day][month repr:short][year]").unwrap();
client.validate_hash(
&self.hash,
format_args!(
"{reference}{paynow_reference}{amount}{status}{poll_url}{token}",
reference = self.reference,
paynow_reference = self.paynow_reference,
amount = self.amount,
status = self.status,
poll_url = self.poll_url,
token = match &self.token {
Some(x) => format!(
"{token}{expiry}",
token = x.token,
expiry = x.expiry.format(&format)?
),
None => String::new(),
},
),
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct MerchantTrace<'a> {
pub(crate) id: u64,
#[serde(rename = "merchanttrace")]
pub(crate) merchant_trace: &'a str,
pub(crate) status: Message,
pub(crate) hash: Secret<Hash>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Token {
token: String,
#[serde(rename = "tokenexpiry")]
expiry: Date,
}
impl Token {
#[must_use]
pub fn token(&self) -> &str {
&self.token
}
#[must_use]
pub fn take_token(self) -> String {
self.token
}
#[must_use]
pub fn expiry(self) -> Date {
self.expiry
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Status {
Created,
Cancelled,
Sent,
#[serde(rename = "Awaiting Delivery")]
AwaitingDelivery,
Delivered,
Paid,
Disputed,
Refunded,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Status::Paid => "Paid",
Status::AwaitingDelivery => "Awaiting Delivery",
Status::Delivered => "Delivered",
Status::Created => "Created",
Status::Sent => "Sent",
Status::Cancelled => "Cancelled",
Status::Disputed => "Disputed",
Status::Refunded => "Refunded",
}
)
}
}
macro_rules! status {
($name:ident) => {
#[derive(Debug, Clone, Copy)]
pub(crate) struct $name;
impl serde::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(stringify!($name))
}
}
impl<'de> serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct StatusVisitor;
impl<'de> Visitor<'de> for StatusVisitor {
type Value = $name;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "the string {}", stringify!($name))
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if s == stringify!($name) {
Result::Ok($name)
} else {
Err(de::Error::invalid_value(Unexpected::Str(s), &self))
}
}
}
deserializer.deserialize_str(StatusVisitor)
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, stringify!($name))
}
}
};
}
status!(Message);
status!(Error);
status!(Ok);
status!(NotFound);