The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Dancer::Plugin::Interchange6;

use strict;
use warnings;

use Dancer qw(:syntax !before !after);
use Dancer::Plugin;
use Dancer::Plugin::DBIC;
use Dancer::Plugin::Auth::Extensible;

use Dancer::Plugin::Interchange6::Cart;
use Dancer::Plugin::Interchange6::Business::OnlinePayment;

use Module::Runtime 'use_module';

=head1 NAME

Dancer::Plugin::Interchange6 - Interchange6 Shop Plugin for Dancer

=head1 VERSION

Version 0.121

=cut

our $VERSION = '0.121';

=head1 DESCRIPTION

This L<Dancer> plugin is now DEPRECATED since all new development has moved
to the L<Dancer2> plugin L<Dancer2::Plugin::Interchange6>.

=head1 REQUIREMENTS

All Interchange6 Dancer applications need to use the L<Dancer::Session::DBIC>
engine.

The easiest way to configure this is in your main module, just after all
the C<use> statements:

   set session => 'DBIC';
   set session_options => {schema => schema};

=head1 CONFIGURATION

Available configuration options:

  plugins:
    Interchange6:
      cart_class: MyApp::Cart
      carts_var_name: some_other_var

=over

=item * cart_class

If you wish to subclass the cart you can have L</shop_cart> return your
subclassed cart instead. You set the cart class via C<cart_class>.
Defaults to L<Dancer::Plugin::Interchange6::Cart>.

=item * carts_var_name

The plugin caches carts in a L<Dancer/var> and the name of the var used can
be set via C<carts_var_name>. Defaults to C<ic6_carts>.

=back

=head1 ROUTES

You can use the L<Dancer::Plugin::Interchange6::Routes> plugin bundled with this
plugin to setup standard routes for:

=over 4

=item product listing

=item product display

=item cart display

=item checkout form

=back

To enable these routes, you put the C<shop_setup_routes> keyword at the end
of your main module:

    package MyShop;

    use Dancer ':syntax';
    use Dancer::Plugin::Interchange6;
    use Dancer::Plugin::Interchange6::Routes;

    get '/shop' => sub {
        ...
    };

    ...

    shop_setup_routes;

    true;

Please refer to L<Dancer::Plugin::Interchange6::Routes> for configuration options
and further information.

=head1 KEYWORDS

=head2 shop_cart

Returns L<Dancer::Plugin::Interchange6::Cart> object.


=head2 shop_charge

Creates payment order and authorizes amount.

=head2 shop_redirect

Calls L<Interchange6::Schema::ResultSet::UriRedirect/redirect> with given args.

=head2 shop_schema

Returns L<Interchange6::Schema> object.

=head2 shop_...

Accessors for L<Interchange6::Schema> result classes. You can use it
to retrieve a single object or the corresponding result set.

    shop_product('F0001')->uri;

    shop_navigation->search({type => 'manufacturer',
                             active => 1});

Available accessors are C<shop_address>, C<shop_attribute>, C<shop_country>,
C<shop_message>, C<shop_navigation>, C<shop_order>, C<shop_product>,
C<shop_state> and C<shop_user>.

=head1 HOOKS

This plugin installs the following hooks:

=head2 Add to cart

The functions registered for these hooks receive the cart object
and the item to be added as parameters.

=over 4

=item before_cart_add_validate

Triggered before item is validated for adding to the cart.

=item before_cart_add

Triggered before item is added to the cart.

=item after_cart_add

Triggered after item is added to the cart.
Used by DBI backend to save item to the database.

=back

=head2 Update cart

The functions registered for these hooks receive the cart object,
the current item in the cart and the updated item.

=over 4

=item before_cart_update

Triggered before cart item is updated (changing quantity).

=item after_cart_update

Triggered after cart item is updated (changing quantity).
Used by DBI backend to update item to the database.

=back

=head2 Remove from cart

The functions registered for these hooks receive the cart object
and the item to be added as parameters.

=over 4

=item before_cart_remove_validate

Triggered before item is validated for removal.
Receives cart object and item SKU.

=item before_cart_remove

Triggered before item is removed from the cart.
Receives cart object and item.

=item after_cart_remove

Triggered after item is removed from the cart.
Used by DBI backend to delete item from the database.
Receives cart object and item.

=back

=head2 Clear cart

=over 4

=item before_cart_clear

Triggered before cart is cleared.

=item after_cart_clear

Triggered after cart is cleared.

=back

=head2 Rename cart

The functions registered for these hooks receive the cart object,
the old name and the new name.

=over 4

=item before_cart_rename

Triggered before cart is renamed.

=item after_cart_rename

Triggered after cart is renamed.

=item before_cart_set_users_id

Triggered before users_id is set for the cart.

=item after_cart_set_users_id

Triggered after users_id is set for the cart.

=item before_cart_set_sessions_id

Triggered before sessions_id is set for the cart.

=item after_cart_set_sessions_id

Triggered after sessions_id is set for the cart.

=back

=head1 EXPIRE DBIC SESSIONS

This command expires/manages DBIC sessions and carts.  NOTE: For proper
functionality please copy/link to Dancer App/bin directory.

    interchange6-expire-sessions

=cut

register_hook(qw/before_cart_add_validate
                 before_cart_add after_cart_add
                 before_cart_update after_cart_update
                 before_cart_remove_validate
                 before_cart_remove after_cart_remove
                 before_cart_rename after_cart_rename
                 before_cart_clear after_cart_clear
                 before_cart_set_users_id after_cart_set_users_id
                 before_cart_set_sessions_id after_cart_set_sessions_id
                /);

register shop_schema => sub {
    _shop_schema(@_);
};

register shop_address => sub {
    _shop_resultset('Address', @_);
};

register shop_attribute => sub {
    _shop_resultset('Attribute', @_);
};

register shop_country => sub {
    _shop_resultset('Country', @_);
};

register shop_message => sub {
    _shop_resultset('Message', @_);
};

register shop_navigation => sub {
    _shop_resultset('Navigation', @_);
};

register shop_order => sub {
    _shop_resultset('Order', @_);
};

register shop_product => sub {
    _shop_resultset('Product', @_);
};

register shop_state => sub {
    _shop_resultset('State', @_);
};

register shop_redirect => sub {
    return resultset('UriRedirect')->redirect($_[0]);
};

register shop_user => sub {
    _shop_resultset('User', @_);
};

register shop_charge => sub {
	my (%args) = @_;
	my ($schema, $bop_object, $payment_settings, $provider, $provider_settings);

	$payment_settings = plugin_setting->{payment};

    die "No payment setting" unless $payment_settings;

    # determine payment provider
    if ( $args{provider} ) {
        $provider = $args{provider};
    }
    else {
        $provider = $payment_settings->{default_provider};
    }

    if (exists $payment_settings->{providers}->{$provider}) {
        $provider_settings = $payment_settings->{providers}->{$provider};
    }
    else {
        die "Settings for provider $provider missing.";
    }

    my %payment_data = (payment_mode => $provider,
                        status => 'request',
                        sessions_id => session->id,
                        payment_action => 'charge',
                        amount => $args{amount},
                        users_id => session('logged_in_user_id'),
                        );

    # create payment order
    $schema = _shop_schema();

    my $payment_order = $schema->resultset('PaymentOrder')->create(\%payment_data);

    # create BOP object wrapper with provider settings
	$bop_object = Dancer::Plugin::Interchange6::Business::OnlinePayment->new($provider, %$provider_settings);

    $bop_object->payment_order($payment_order);

    # call charge method
    $bop_object->charge(%args);

    if ($bop_object->is_success) {
        $payment_order->update({
            status => 'success',
            auth_code => $bop_object->authorization,
        });
    }
    else {
        $payment_order->update({
            status => 'failure',
	    payment_error_code => $bop_object->error_code,
	    payment_error_message => $bop_object->error_message,
        });
    }

	return $bop_object;
};

register cart => \&_shop_cart;
register shop_cart => \&_shop_cart;

sub _shop_cart {

    my ( %args, $user_ref );

    # cart name from arg or default 'main'
    $args{name} = @_ == 1 ? $_[0] : 'main';

    # set name of var we will stash carts in
	my $var = plugin_setting->{carts_var_name} || 'ic6_carts';
    debug "carts_var_name: $var";

    # cart class
    my $cart_class = plugin_setting->{cart_class}
      || 'Dancer::Plugin::Interchange6::Cart';

    my $carts = var($var) || {};

    if ( !defined $carts->{ $args{name} } ) {

        # can't find this cart in stash

        $args{sessions_id} = session->id;

        if ( $user_ref = logged_in_user ) {

            # user is logged in
            $args{users_id} = $user_ref->users_id;
        }

        $carts->{ $args{name} } = use_module($cart_class)->new(%args);
    }

    # stash carts back in var
    var $var => $carts;

    return $carts->{ $args{name} };
}

sub _shop_schema {
    my $schema_key;

    if (@_) {
        $schema_key = $_[0];
    }
    else {
        $schema_key = 'default';
    }

    return schema($schema_key);
};

sub _shop_resultset {
    my ($name, $key) = @_;

    if (defined $key) {
        return resultset($name)->find($key);
    }

    return resultset($name);
};

register_plugin;

=head1 ACKNOWLEDGEMENTS

The L<Dancer> developers and community for their great application framework
and for their quick and competent support.

Peter Mottram for his patches.

=head1 LICENSE AND COPYRIGHT

Copyright 2010-2016 Stefan Hornburg (Racke).

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.

=head1 SEE ALSO

L<Dancer2::Plugin::Interchange6>

L<Interchange6>, L<Interchange6::Schema>

=cut

1;