checkout_core 0.0.147

core traits and structs for the checkout_controller crate
Documentation
use crate::checkout::Checkout;
use crate::context::Context;
use crate::error::new_application_error;
use crate::error::new_invalid_state_error;
use crate::error::new_not_found_error;
use crate::error::Error;
use crate::internal_context::InternalContext;
use crate::payment::cancel as cancel_payment;
use crate::payment::process as process_payment;
use crate::payment::Initiator;
use crate::payment::Payment;
use crate::payment::PaymentProcessor;

use super::reconcile;
use super::LookupKey;
use super::Order;
use super::OrderState;

pub async fn get_handler<C: Context + Send>(
    ctx: &mut C,
    id: &str,
) -> Result<Order<Payment<<C as PaymentProcessor>::Data>>, Error> {
    let maybe_od = ctx.get_order(id).await?;

    if maybe_od.is_none() {
        return Err(new_not_found_error(
            "the requested order could not be found",
        ));
    }

    let mut od = maybe_od.unwrap();
    od.populate_associations(ctx).await?;

    Ok(od)
}

pub async fn step_payment_handler<C: Context + Send>(
    internal_ctx: &InternalContext,
    ctx: &mut C,
    order_id: &str,
    args: <C as PaymentProcessor>::ProcessArgs,
) -> Result<Order<Payment<<C as PaymentProcessor>::Data>>, Error> {
    // first retrieve the associated payment for update to avoid deadlock
    let maybe_payment = get_order_payment_for_update(ctx, order_id).await?;

    let maybe_od = ctx.get_order_for_update(order_id).await?;
    if maybe_od.is_none() {
        return Err(new_not_found_error(
            "the requested order could not be found",
        ));
    }

    let mut od = maybe_od.unwrap();

    match od.state {
        OrderState::PaymentInProgress { reserved_since: _ } => {
            let mut payment = maybe_payment.ok_or(new_not_found_error(
                "the associated payment could not be found",
            ))?;

            // check that the order payment hasn't changed
            if &payment.id != od.payment_id.as_ref().unwrap() {
                return Err(new_application_error("STALE_PAYMENT", "the associated payment was changed by another process while this transaction was in progress. please try again."));
            }

            process_payment(
                internal_ctx,
                ctx,
                &mut payment,
                Initiator::Order(&mut od),
                args,
            )
            .await?;

            od.populate_associations(ctx).await?;
            Ok(od)
        }
        _ => Err(new_invalid_state_error(
            "order must be in the PaymentInProgress state",
        )),
    }
}

pub async fn confirm_handler<C: Context + Send>(
    internal_ctx: &InternalContext,
    ctx: &mut C,
    order_id: &str,
) -> Result<Order<Payment<<C as PaymentProcessor>::Data>>, Error> {
    let maybe_od = ctx.get_order_for_update(order_id).await?;
    if maybe_od.is_none() {
        return Err(new_not_found_error(
            "the requested order could not be found",
        ));
    }

    let mut od = maybe_od.unwrap();
    od.confirm(internal_ctx, ctx).await?;
    od.populate_associations(ctx).await?;

    Ok(od)
}

pub async fn complete_handler<C: Context + Send>(
    internal_ctx: &InternalContext,
    ctx: &mut C,
    order_id: &str,
) -> Result<Order<Payment<<C as PaymentProcessor>::Data>>, Error> {
    let maybe_od = ctx.get_order_for_update(order_id).await?;
    if maybe_od.is_none() {
        return Err(new_not_found_error(
            "the requested order could not be found",
        ));
    }

    let mut od = maybe_od.unwrap();
    od.complete(internal_ctx, ctx).await?;
    od.populate_associations(ctx).await?;

    Ok(od)
}

pub async fn cancel_handler<C: Context + Send>(
    internal_ctx: &InternalContext,
    ctx: &mut C,
    order_id: &str,
) -> Result<Order<Payment<<C as PaymentProcessor>::Data>>, Error> {
    // first retrieve the associated payment for update to avoid deadlock
    let maybe_payment = get_order_payment_for_update(ctx, order_id).await?;

    let maybe_od = ctx.get_order_for_update(order_id).await?;
    if maybe_od.is_none() {
        return Err(new_not_found_error(
            "the requested order could not be found",
        ));
    }

    let mut od = maybe_od.unwrap();

    match od.state {
        OrderState::Cancelled => {}
        OrderState::PaymentInProgress { reserved_since: _ } => {
            let mut payment = maybe_payment.ok_or(new_not_found_error(
                "the associated payment could not be found",
            ))?;

            // check that the order payment hasn't changed
            if &payment.id != od.payment_id.as_ref().unwrap() {
                return Err(new_application_error("STALE_PAYMENT", "the associated payment was changed by another process while this transaction was in progress. please try again."));
            }

            cancel_payment(internal_ctx, ctx, &mut payment, Initiator::Order(&mut od)).await?;
        }
        _ => {
            od.cancel(internal_ctx, ctx).await?;
        }
    }

    od.populate_associations(ctx).await?;
    Ok(od)
}

pub async fn reconcile_handler<C: Context + Send>(
    ctx: &mut C,
    lookup_key: &LookupKey,
) -> Result<(), Error> {
    let maybe_order: Option<Order<Payment<<C as PaymentProcessor>::Data>>> = match &lookup_key {
        LookupKey::CheckoutId(checkout_id) => get_order_by_checkout_id(ctx, checkout_id).await?,
        LookupKey::OrderId(order_id) => ctx.get_order_for_update(order_id).await?,
    };

    if maybe_order.is_none() {
        // this isn't supposed to happen
        return Err(new_not_found_error(
            "the requested order could not be found",
        ));
    }

    let mut od = maybe_order.unwrap();

    reconcile(ctx, &mut od).await
}

async fn get_order_by_checkout_id<C: Context + Send>(
    ctx: &mut C,
    checkout_id: &str,
) -> Result<Option<Order<Payment<<C as PaymentProcessor>::Data>>>, Error> {
    let maybe_checkout: Option<Checkout<Payment<<C as PaymentProcessor>::Data>>> =
        ctx.get_checkout_for_update(checkout_id).await?;
    if maybe_checkout.is_none() {
        return Err(new_not_found_error(
            "the associated checkout could not be found",
        ));
    }

    let checkout = maybe_checkout.unwrap();
    if checkout.order_id.is_none() {
        return Err(new_application_error(
            "INVALID_ASSOCIATED_CHECKOUT",
            "there is no order associated with the checkout",
        ));
    }
    ctx.get_order_for_update(&checkout.order_id.unwrap()).await
}

async fn get_order_payment_for_update<C: Context + Send>(
    ctx: &mut C,
    order_id: &str,
) -> Result<Option<Payment<<C as PaymentProcessor>::Data>>, Error> {
    let maybe_od: Option<Order<Payment<<C as PaymentProcessor>::Data>>> =
        ctx.get_order(order_id).await?;
    if maybe_od.is_none() {
        return Err(new_not_found_error(
            "the requested order could not be found",
        ));
    }

    let od = maybe_od.unwrap();
    if od.payment_id.is_none() {
        return Ok(None);
    }
    ctx.get_payment_for_update(od.payment_id.as_ref().unwrap())
        .await
}