use async_trait::async_trait;
use cloud_pubsub;
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::checkout::Checkout;
use crate::context::Context;
use crate::error::{new_application_error, Error};
use crate::internal_context::InternalContext;
use crate::money::Money;
use crate::order::Order;
pub mod handlers;
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct Payment<Data> {
pub id: String,
pub state: PaymentState,
pub initiator_id: InitiatorId,
pub charge_amount: Money,
pub correlation_id: Option<String>,
pub data: Option<Data>,
}
#[derive(Serialize, Deserialize, JsonSchema)]
#[serde(tag = "name", rename_all = "snake_case")]
pub enum PaymentState {
Incomplete,
Succeeded,
Cancelled,
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub enum InitiatorId {
Checkout(String),
Order(String),
}
pub enum Initiator<'a, P: Sync + Send> {
Checkout(&'a mut Checkout<P>),
Order(&'a mut Order<P>),
}
#[derive(Serialize, Deserialize)]
pub struct CallbackArgs<T> {
pub correlation_id: String,
pub process_args: T,
}
impl<T: DeserializeOwned> cloud_pubsub::FromPubSubMessage for CallbackArgs<T> {
fn from(message: cloud_pubsub::EncodedMessage) -> Result<Self, cloud_pubsub::error::Error> {
match message.decode() {
Ok(bytes) => {
let val: CallbackArgs<T> = serde_json::from_slice(&bytes).unwrap();
Ok(val)
}
Err(e) => Err(cloud_pubsub::error::Error::from(e)),
}
}
}
#[async_trait]
pub trait PaymentProcessor {
type Data: Sync + Send + Serialize + DeserializeOwned;
type InitArgs: Sync + Send + Serialize + DeserializeOwned;
type ProcessArgs: Sync + Send + Serialize + DeserializeOwned;
async fn initiate_payment(
&mut self,
_payment: &Payment<Self::Data>,
_args: &Self::InitArgs,
) -> Result<PaymentData<Self::Data>, Error> {
Ok(PaymentData {
correlation_id: None,
data: None,
})
}
async fn process_payment(
&mut self,
payment: &Payment<Self::Data>,
args: &Self::ProcessArgs,
) -> Result<ProcessingResult<Self::Data>, Error>;
async fn cancel_payment(
&mut self,
payment: &Payment<Self::Data>,
) -> Result<CancellationResult, Error>;
}
#[async_trait]
pub trait PaymentStore: PaymentProcessor {
async fn create_payment(
&mut self,
payment: &Payment<<Self as PaymentProcessor>::Data>,
) -> Result<(), Error>;
async fn update_payment(
&mut self,
payment: &Payment<<Self as PaymentProcessor>::Data>,
) -> Result<(), Error>;
async fn get_payment<P: Sync + Send + Serialize + DeserializeOwned>(
&mut self,
id: &str,
) -> Result<Option<P>, Error>;
async fn get_payment_for_update<P: Sync + Send + Serialize + DeserializeOwned>(
&mut self,
id: &str,
) -> Result<Option<P>, Error>;
async fn get_payment_for_update_by_correlation_id<
P: Sync + Send + Serialize + DeserializeOwned,
>(
&mut self,
_id: &str,
) -> Result<Option<P>, Error> {
Ok(None)
}
}
pub struct ProcessingResult<Data> {
pub state: PaymentState,
pub payment_data: PaymentData<Data>,
}
pub struct CancellationResult {
pub success: bool,
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct PaymentData<Data> {
pub correlation_id: Option<String>,
pub data: Option<Data>,
}
async fn new<'a, C: Context + Send, P: Sync + Send + Serialize + DeserializeOwned>(
ctx: &mut C,
initiator: Initiator<'a, P>,
charge_amount: Money,
args: <C as PaymentProcessor>::InitArgs,
) -> Result<Payment<<C as PaymentProcessor>::Data>, Error> {
let mut payment = Payment {
id: Uuid::new_v4().to_string(),
state: PaymentState::Incomplete,
initiator_id: match &initiator {
Initiator::Checkout(co) => InitiatorId::Checkout(co.id.clone()),
Initiator::Order(od) => InitiatorId::Order(od.id.clone()),
},
charge_amount,
correlation_id: None,
data: None,
};
let payment_data = ctx.initiate_payment(&payment, &args).await?;
payment.correlation_id = payment_data.correlation_id;
payment.data = payment_data.data;
ctx.create_payment(&payment).await?;
Ok(payment)
}
pub async fn process<'a, C: Context + Send, P: Sync + Send + Serialize + DeserializeOwned>(
internal_ctx: &InternalContext,
ctx: &mut C,
payment: &mut Payment<<C as PaymentProcessor>::Data>,
initiator: Initiator<'a, P>,
args: <C as PaymentProcessor>::ProcessArgs,
) -> Result<(), Error> {
match &payment.state {
PaymentState::Succeeded | PaymentState::Cancelled => {
return Err(new_application_error(
"PAYMENT_COMPLETED",
"the payment has already completed",
))
}
_ => {}
}
let result = ctx.process_payment(payment, &args).await?;
payment.correlation_id = result.payment_data.correlation_id;
payment.data = result.payment_data.data;
payment.state = result.state;
ctx.update_payment(payment).await?;
match payment.state {
PaymentState::Succeeded => match initiator {
Initiator::Checkout(co) => co.complete(internal_ctx, ctx).await,
Initiator::Order(od) => od.prompt_confirmation(internal_ctx, ctx).await,
},
PaymentState::Cancelled => match initiator {
Initiator::Checkout(co) => co.release(ctx).await,
Initiator::Order(od) => od.cancel(internal_ctx, ctx).await,
},
_ => Ok(()),
}
}
pub async fn cancel<'a, C: Context + Send, P: Sync + Send + Serialize + DeserializeOwned>(
internal_ctx: &InternalContext,
ctx: &mut C,
payment: &mut Payment<<C as PaymentProcessor>::Data>,
initiator: Initiator<'a, P>,
) -> Result<(), Error> {
match &payment.state {
PaymentState::Cancelled => Ok(()),
PaymentState::Incomplete => {
let cancellation_result = ctx.cancel_payment(payment).await?;
if !cancellation_result.success {
return Err(new_application_error(
"CANCELLATION_UNAVAILABLE",
"the payment can no longer be cancelled",
));
}
payment.state = PaymentState::Cancelled;
ctx.update_payment(payment).await?;
match initiator {
Initiator::Checkout(co) => co.release(ctx).await,
Initiator::Order(od) => od.cancel(internal_ctx, ctx).await,
}
}
PaymentState::Succeeded => Err(new_application_error(
"PAYMENT_COMPLETED",
"the payment has already completed",
)),
}
}