The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Handel::Checkout - Checkout Pipeline Processor

SYNOPSIS

    use Handel::Checkout;
    use strict;
    use warnings;
    
    my $checkout = Handel::Checkout->new({
        cart    => '122345678-9098-7654-3212-345678909876',
        phases  => [CHECKOUT_PHASE_INITIALIZE, CHECKOUT_PHASE_VALIDATE]
    });
    
    if ($checkout->process == CHECKOUT_STATUS_OK) {
        print 'Your order number is ', $checkout->order->number;
    } else {
        ...
    };

DESCRIPTION

Handel::Checkout is a basic pipeline processor that uses plugins at various phases to perform any work necessary from credit card authorization to order delivery. Handel does not try to be all things to all people needing to place online orders. Instead, it provides a basic plugin mechanism allowing the checkout process to be customized for many different needs.

CONSTRUCTOR

new

Arguments: \%options

Creates a new checkout pipeline process and loads all available plugins in the default plugin namespace (Handel::Checkout::Plugin). new accepts the following options in an optional HASH reference:

cart

A HASH reference, a class object, or a cart primary key value. This will be loaded into a new order object and associated with the new checkout process. By default, a Handel::Order object will be create unless you have set order_class to another class.

See cart below for further details about the various values allowed to be passed.

Note: When creating a new order via Handel::Order, new will automatically create a checkout process and process the CHECKOUT_PHASE_INITIALIZE phase. However, when a new order is created using cart in Handel::Checkout, the automatic processing of CHECKOUT_PHASE_INITIALIZE is disabled.

order

A HASH reference, an order object, or an order primary key value. This will be loaded and associated with the new checkout process. By default, a Handel::Order object will be create when you pass in an order id unless you have set order_class to another class.

See order below for further details about the various values allowed to be passed.

pluginpaths

An array reference or a comma (or space) separated list containing the various namespaces of plugins to be loaded. This will override any settings in ENV or httpd.conf for the current checkout object only.

    my $checkout = Handel::Checkout->new({
        pluginpaths => [MyNamespace::Plugins, Other::Plugin]
    });
    
    my $checkout = Handel::Checkout->new({
        pluginpaths => 'MyNamespace::Plugins, Other::Plugin'
    });

See "HandelPluginPaths" for more information about settings/resetting plugin search paths.

addpluginpaths

An array reference or a comma (or space) separated list containing the various namespaces of plugin paths in addition to Handel::Checkout::Plugin to be loaded. If HandelAddPluginPaths is also specified, the two will be combined.

    my $checkout = Handel::Checkout->new({
        addpluginpaths => [MyNamespace::Plugins, Other::Plugin]
    });
    
    my $checkout = Handel::Checkout->new({
        addpluginpaths => 'MyNamespace::Plugins, Other::Plugin'
    });

See "HandelAddPluginPaths" for more information about settings/resetting plugin search paths.

loadplugins

An array reference or a comma (or space) separated list containing the names of the specific plugins to load in the current plugin paths.

See "HandelLoadPlugins" for more information about loading specific plugins.

ignoreplugins

An array reference or a comma (or space) separated list containing the names of the specific plugins to be ignored (not loaded) in the current plugin paths.

See "HandelIgnorePlugins" for more information about ignore specific plugins.

phases

An array reference or a comma (or space) separated list containing the various phases to be executed.

    my $checkout = Handel::Checkout->new({
        phases => [CHECKOUT_PHASE_VALIDATE,
                   CHECKOUT_PHASE_AUTHORIZE]
    });
    
    my $checkout = Handel::Checkout->new({
        phases => 'CHECKOUT_PHASE_VALIDATE, CHECKOUT_PHASE_AUTHORIZE'
    });
stash

A Handel::Checkout::Stash object, subclass or a hash reference. If nothing is specified, stash_class will be used instead.

    my $checkout = Handel::Checkout->new({
        stash => { key => 'value' }
    });
    
    my $checkout = Handel::Checkout->new({
        stash => $stash
    });

METHODS

add_handler

Arguments: $phase, \&coderef, $preference

Registers a code reference with the checkout phase specified and assigns a run order preference. This is usually called within register on the current checkout context within a plugin:

    sub register {
        my ($self, $ctx) = @_;

        $ctx->add_handler(CHECKOUT_PHASE_DELIVER, \&myhandler, 200);
    };
    
    sub myhandler {
        ...
    };

If no preference number is specified, the handler is added to the end of the list after all other handlers in that phase.

If there is already a handler in the specified phase with the same preference, a Handel::Exception::Checkout exception will be thrown.

While not enforced, please keep your handler preference orders between 251 - 749. Preference orders 1-250 and 750-1000 will be reserved for core modules that need to run before or after all other plugin handlers.

add_message

Arguments: $message

Adds a new text message or Handel::Checkout::Message based object to the message stack so plugins can log their issues for later inspection.

    sub handler {
        my ($self, $ctx) = @_;
        ...
        $ctx->add_message('Skipping phase for countries other than US...');
    
        return CHECKOUT_HANDLER_DECLINE;
    };

You can subclass Handel::Checkout::Message to add your own properties. If your adding a simple text message, a new Handel::Checkout::Message object will automatically be created and package, filename, and line properties will be set.

add_phase

Arguments: $name, $value [, $import]

Adds a new constant/sub named $name to Handel::Constant and adds the $value to CHECKOUT_ALL_PHASES. The new phase will be accepted by constraint_checkout_phase and can be used by checkout plugins registering their handlers via add_handler($phase, &handler). If $import is true, add_phase will register the constant in the local namespace just as if it you had specified it in your use statement.

    use Handel::Checkout;
    
    Handel::Checkout->add_phase('CHECKOUT_PHASE_CUSTOMPHASE', 42, 1);
    
    print constraint_checkout_phase(&CHECKOUT_PHASE_CUSTOMPHASE);
    
    $plugincontext->add_handler(
        Handel::Constants::CHECKOUT_PHASE_CUSTOMPHASE, &handlersub
    );

clear_messages

Clears all messages from the current checkout object.

cart

Creates a new order object from the specified cart and associates that order with the current checkout process. This is typically only needed the first time you want to run checkout for a specific cart. From then on, you only need to load the already created order using order below.

By default, a Handel::Order object will be create unless you have set order_class to another class. When the order class loads a cart by id, it will create a cart using the cart_class set in the order_class.

cart can accept one of three possible parameter values:

cart(\%filter)

When you pass a HASH reference, cart will attempt to load all available carts. If multiple carts are found, only the first one will be used.

    $checkout->cart({
        shopper => '12345678-9098-7654-3212-345678909876',
        type => CART_TYPE_TEMP
    });
cart($cart)

You can also pass in an already existing Handel::Cart object or subclass. It will then be loaded into a new order object and associated with the current checkout process.

    my $cart = Handel::Cart->search({
        id => '12345678-9098-7654-3212-345678909876'
    })->first;
    
    $checkout->cart($cart);
cart('11111111-1111-1111-1111-111111111111')

Finally, you can pass a valid cart/uuid into cart. The matching cart will be loaded into a new order object and associated with the current checkout process.

    $checkout->cart('12345678-9098-7654-3212-345678909876');

order_class

Arguments: $order_class

Gets/sets the name of the class to use when loading an existing order into the checkout process or when creating a new order from an existing cart. By default, it loads order using Handel::Order. While you can set this directly in your application, it's best to set it in a custom subclass of Handel::Checkout.

    package CustomCheckout;
    use strict;
    use warnings;
    use base qw/Handel::Checkout/;
    __PACKAGE__->order_class('CustomOrder');
    
    ...
    use CustomCheckout;
    my $checkout = CustomCheckout->new({
        order => '11111111-2222-3333-4444-555555555555'
    });
    
    print ref $checkout->order; # CustomOrder

stash_class

Arguments: $stash_class

Gets/sets the name of the stash class to create during new. By default, it returns Handel::Checkout::Stash. While you can set this directly in your application, it's best to set it in a custom subclass of Handel::Checkout.

    package CustomCheckout;
    use strict;
    use warnings;
    use base qw/Handel::Checkout/;
    __PACKAGE__->stash_class('MyCustomStash');

messages

Returns a reference to an array in scalar context of Handel::Checkout::Message objects containing additional information about plugin and other checkout decisions and activities. Returns a list in list context.

    foreach ($checkout->messages) {
        warn $_->text, "\n";
    };

load_plugins

Arguments: \%options

Returns a list of plugins pre-instantiated that match the options specified. The plugins returned are not yet registered.

See "new" for the available plugin/plugin path options.

plugins

Returns a list plugins loaded for current checkout object in list context:

    my $checkout = Handel::Checkout->new;
    my @plugins = $checkout->plugins;
    
    foreach (@plugins) {
        $_->cleanup_or_something;
    };

Returns an array reference in scalar context.

order

Gets/sets an existing order object with the existing checkout process. By default, a Handel::Order object will be create unless you have set order_class to another class.

order can accept one of three possible parameter values:

order(\%filter)

When you pass a HASH reference, order will attempt to load all available orders. If multiple order are found, only the first one will be used.

    $checkout->order({
        shopper => '12345678-9098-7654-3212-345678909876',
        id => '11111111-2222-3333-4444-5555666677778888'
    });
order($order)

You can also pass in an already existing Handel::Order object or subclass. It will then be associated with the current checkout process.

    my $order = Handel::Order->search({
        id => '12345678-9098-7654-3212-345678909876'
    })->first;
    
    $checkout->order($order);
order('11111111-1111-1111-1111-111111111111')

Finally, you can pass a valid order/uuid into order. The matching order will be loaded and associated with the current checkout process.

    $checkout->order('12345678-9098-7654-3212-345678909876');

phases

Arguments: \@phases

Gets/sets the phases active for the current checkout process. This can be an array reference or a comma (or space) separated string:

    $checkout->phases([
        CHECKOUT_PHASE_INITIALIZE,
        CHECKOUT_PHASE_VALIDATE
    ]);
    
    $checkout->phases('CHECKOUT_PHASE_INITIALIZE, CHECKOUT_PHASE_VALIDATE']);

No attempt is made to sanitize the array for duplicates or the order of the phases. This means you can do evil things like run a phase twice, or run the phases out of order. Returns a list in list context and an array reference in scalar context.

process

Arguments: \@phases

Executes the current checkout process pipeline and returns CHECKOUT_STATUS_*. Any plugin handler that doesn't return CHECKOUT_HANDLER_OK or CHECKOUT_HANDLER_DECLINE is considered to be an error that the checkout process is aborted.

Just like phases, you can pass an array reference or a comma (or space) separated string of phases into process.

The clear method is called on the stash before the call to setup on each plugin so plugins can set stash data, and the stash remains until the next call to process so $plugin->teardown can read any remaining stash data before process ends.

The call to process will return one of the following constants:

CHECKOUT_STATUS_OK

All plugin handlers were called and returned CHECKOUT_HANDLER_OK or CHECKOUT_HANDLER_DECLINE

CHECKOUT_STATUS_ERROR

At least one plugin failed to return or an error occurred while processing the registered plugin handlers.

stash

Returns a stash object that can store information shared by all plugins in the current context. By default, a Handel::Checkout::Stash object will be create unless you have set stash_class to another class.

    # plugin handler
    my ($self, $ctx) = @_;

    $ctx->stash->{'template'} = 'template.tt';

get_component_class

Arguments: $name

Gets the current class for the specified component name.

    my $class = $self->get_component_class('cart_class');

There is no good reason to use this. Use the specific class accessors instead.

set_component_class

Arguments: $name, $value

Sets the current class for the specified component name.

    $self->set_component_class('cart_class', 'MyCartClass');

A Handel::Exception exception will be thrown if the specified class can not be loaded.

There is no good reason to use this. Use the specific class accessors instead.

CONFIGURATION

HandelPluginPaths

This resets the checkout plugin search path to a namespace of your choosing, The default plugin search path is Handel::Checkout::Plugin::*

    PerlSetVar HandelPluginPaths MyApp::Plugins

In the example above, the checkout plugin search path will load all plugins in the MyApp::Plugins::* namespace (but not MyApp::Plugin itself). Any plugins in Handel::Checkout::Plugin::* will be ignored.

You can also pass a comma or space separate list of namespaces.

    PerlSetVar HandelPluginPaths 'MyApp::Plugins, OtherApp::Plugins'

Any plugin found in the search path that isn't a subclass of Handel::Checkout::Plugin will be ignored.

HandelAddPluginPaths

This adds an additional plugin search paths. This can be a comma or space separated list of namespaces.

    PerlSetVar HandelAddPluginPaths  'MyApp::Plugins, OtherApp::Plugins'

In the example above, when a checkout process is loaded, it will load all plugins in the Handel::Checkout::Plugin::*, MyApp::Plugins::*, and OtherApp::Plugins namespaces.

Any plugin found in the search path that isn't a subclass of Handel::Checkout::Plugin will be ignored.

HandelIgnorePlugins

This is a comma/space separated list [or an anonymous array, or a regex outside of httpd.conf] of plugins to ignore when loading all available plugins in the given namespaces.

    PerlSetVar HandelIgnorePlugins 'Handel::Checkout::Plugin::Initialize'

    $ENV{'HandelIgnorePlugins'} = 'Handel::Checkout::Plugin::Initialize';
    $ENV{'HandelIgnorePlugins'} = ['Handel::Checkout::Plugin::Initialize'];
    $ENV{'HandelIgnorePlugins'} = qr/^Handel::Checkout::Plugin::(Initialize|Validate)$/;

If the Handel::Checkout::Plugin namespace has the following modules:

    Handel::Checkout::Plugin::Initialize
    Handel::Checkout::Plugin::ValidateAddress
    Handel::Checkout::Plugin::FaxDelivery
    Handel::Checkout::Plugin::EmailDelivery

all of the modules above will be loaded <b>except</b> Handel::Checkout::Plugin::Initialize. All plugins in any other configured namespaces will be loaded.

If both HandelLoadPlugins and HandelIgnorePlugins are specified, only the plugins in HandelLoadPlugins will be loaded, unless they are also in HandelIgnorePlugins in which case they will be ignored.

HandelLoadPlugins

This is a comma or space separated list [or an anonymous array, or a regex outside of httpd.conf] of plugins to be loaded from the available namespaces.

    PerlSetVar HandelLoadPlugins 'Handel::Checkout::Plugin::ValidateAddress'

    $ENV{'HandelLoadPlugins'} = 'Handel::Checkout::Plugin::ValidateAddress';
    $ENV{'HandelLoadPlugins'} = ['Handel::Checkout::Plugin::ValidateAddress'];
    $ENV{'HandelLoadPlugins'} = qr/^Handel::Checkout::Plugin::(ValidateAddress|Authorize)$/;

If the following plugins are available in all configured namespaces:

    Handel::Checkout::Plugin::Initialize
    Handel::Checkout::Plugin::ValidateAddress
    Handel::Checkout::Plugin::FaxDelivery
    Handel::Checkout::Plugin::EmailDelivery
    MyApp::Plugin::VerifiedByVisa
    MyApp::Plugin::WarehouseUpdate

only Handel::Checkout::Plugin::ValidateAddress will be loaded. All other plugins in all configured namespaces will be ignored.

If both HandelLoadPlugins and HandelIgnorePlugins are specified, only the plugins in HandelLoadPlugins will be loaded, unless they are also in HandelIgnorePlugins in which case they will be ignored.

CAVEATS

[I think] Due to the localization of AutoCommit to coerce disabling of autoupdates during process, Always access orders and order items from their checkout parent once they've been assigned to the checkout process, and not any available reference:

    my $order = Handel::Order->create({billtofirstname => 'Chris'});
    my $checkout = Handel::Checkout->new({order => $order});

    # some plugin alters billtofirstname to 'Christopher'
    $checkout->process;

    $order->billtofirstname; #Chris
    $checkout->order->billtofirstname; #Christopher

SEE ALSO

Handel::Constants, Handel::Checkout::Plugin, Handel::Order

AUTHOR

    Christopher H. Laco
    CPAN ID: CLACO
    claco@chrislaco.com
    http://today.icantfocus.com/blog/