checkout_core 0.0.147

core traits and structs for the checkout_controller crate
Documentation
use crate::checkout::{Checkout, State};
use crate::context::Context;
use crate::customer::{Address, Contact};
use crate::error::{new_application_error, new_invalid_state_error, new_not_found_error, Error};
use crate::internal_context::InternalContext;
use crate::money::Currency;
use crate::payment::cancel as cancel_payment;
use crate::payment::process as process_payment;
use crate::payment::PaymentProcessor;
use crate::payment::{Initiator, Payment};
use crate::shipping::FulfillmentSelection;

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

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

    let mut co = maybe_co.unwrap();
    co.populate_associations(ctx).await?;

    Ok(co)
}

pub async fn create_handler<C: Context + Send>(
    ctx: &mut C,
    currency: Currency,
    contact: Option<Contact>,
    shipping_address: Option<Address>,
) -> Result<Checkout<Payment<<C as PaymentProcessor>::Data>>, Error> {
    let mut co = Checkout::new(ctx, currency, contact, shipping_address).await?;
    co.populate_associations(ctx).await?;

    Ok(co)
}

pub async fn add_item_handler<C: Context + Send>(
    ctx: &mut C,
    checkout_id: &str,
    sku: String,
    quantity: u64,
) -> Result<Checkout<Payment<<C as PaymentProcessor>::Data>>, Error> {
    let maybe_co = ctx.get_checkout_for_update(&checkout_id).await?;
    if maybe_co.is_none() {
        return Err(new_not_found_error(
            "the requested checkout could not be found",
        ));
    }

    let mut co = maybe_co.unwrap();

    co.add_item(ctx, sku, quantity).await?;
    co.populate_associations(ctx).await?;

    Ok(co)
}

pub async fn remove_item_handler<C: Context + Send>(
    ctx: &mut C,
    checkout_id: &str,
    sku: String,
    quantity: u64,
) -> Result<Checkout<Payment<<C as PaymentProcessor>::Data>>, Error> {
    let maybe_co = ctx.get_checkout_for_update(&checkout_id).await?;
    if maybe_co.is_none() {
        return Err(new_not_found_error(
            "the requested checkout could not be found",
        ));
    }

    let mut co = maybe_co.unwrap();

    co.remove_item(ctx, sku, quantity).await?;
    co.populate_associations(ctx).await?;

    Ok(co)
}

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

    let mut co = maybe_co.unwrap();

    co.confirm_items(ctx).await?;
    co.populate_associations(ctx).await?;

    Ok(co)
}

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

    let mut co = maybe_co.unwrap();

    co.update_contact(ctx, contact).await?;
    co.populate_associations(ctx).await?;

    Ok(co)
}

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

    let mut co = maybe_co.unwrap();

    co.update_shipping_address(ctx, shipping_address).await?;
    co.populate_associations(ctx).await?;

    Ok(co)
}

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

    let mut co = maybe_co.unwrap();

    co.update_fulfillment(ctx, fulfillment).await?;
    co.populate_associations(ctx).await?;

    Ok(co)
}

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

    let mut co = maybe_co.unwrap();

    co.initiate_payment(ctx, args).await?;
    co.populate_associations(ctx).await?;

    Ok(co)
}

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

    let maybe_co = ctx.get_checkout_for_update(checkout_id).await?;
    if maybe_co.is_none() {
        return Err(new_not_found_error(
            "the requested checkout could not be found",
        ));
    }

    let mut co = maybe_co.unwrap();

    match co.state {
        State::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 != co.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::Checkout(&mut co),
                args,
            )
            .await?;

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

pub async fn initiate_order_handler<C: Context + Send>(
    internal_ctx: &InternalContext,
    ctx: &mut C,
    checkout_id: &str,
    args: <C as PaymentProcessor>::InitArgs,
) -> Result<Checkout<Payment<<C as PaymentProcessor>::Data>>, Error> {
    let maybe_co = ctx.get_checkout_for_update(&checkout_id).await?;
    if maybe_co.is_none() {
        return Err(new_not_found_error(
            "the requested checkout could not be found",
        ));
    }

    let mut co = maybe_co.unwrap();

    co.initiate_order(internal_ctx, ctx, args).await?;
    co.populate_associations(ctx).await?;

    Ok(co)
}

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

    let maybe_co = ctx.get_checkout_for_update(checkout_id).await?;
    if maybe_co.is_none() {
        return Err(new_not_found_error(
            "the requested checkout could not be found",
        ));
    }

    let mut co = maybe_co.unwrap();

    match co.state {
        State::Shopping => {}
        State::ItemsConfirmed{ reserved_since: _ } => {
            co.release(ctx).await?;
        }
        State::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 != co.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::Checkout(&mut co),
            )
            .await?;
        }
        _ => {
            return Err(new_invalid_state_error(
                "must be in one of Shopping, ItemsConfirmed or PaymentInProgress states",
            ))
        }
    }

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

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

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