File Coverage

blib/lib/Dancer2/Plugin/Interchange6.pm
Criterion Covered Total %
statement 81 81 100.0
branch 19 20 95.0
condition 4 4 100.0
subroutine 22 22 100.0
pod 13 14 92.8
total 139 141 98.5


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::Interchange6;
2              
3 3     3   1066077 use strict;
  3         4  
  3         73  
4 3     3   9 use warnings;
  3         5  
  3         63  
5              
6 3     3   1415 use Dancer2::Plugin;
  3         174585  
  3         21  
7 3     3   27376 use Dancer2::Plugin::Interchange6::Business::OnlinePayment;
  3         9  
  3         12  
8 3     3   110 use Module::Runtime 'use_module';
  3         13  
  3         22  
9 3     3   119 use Scalar::Util 'weaken';
  3         5  
  3         2724  
10              
11             =head1 NAME
12              
13             Dancer2::Plugin::Interchange6 - Interchange6 Shop Plugin for Dancer2
14              
15             =head1 VERSION
16              
17             Version 0.203
18              
19             =cut
20              
21             our $VERSION = '0.203';
22              
23             =head1 REQUIREMENTS
24              
25             All Interchange6 Dancer2 applications need to use the L<Dancer2::Session::DBIC>
26             engine.
27              
28             The easiest way to configure this is in your C<config.yml> (or whatever other
29             configuration file you prefer):
30              
31             plugins
32             DBIC:
33             default:
34             schema_class: Interchange6::Schema
35             # ... other DBIC plugin config here
36             engines:
37             session:
38             DBIC:
39             db_connection_name: default # connection name from DBIC plugin
40             session: DBIC
41              
42             =head1 CONFIGURATION
43              
44             Available configuration options:
45              
46             plugins:
47             Interchange6:
48             cart_class: MyApp::Cart
49             carts_var_name: some_other_var
50              
51             =over
52              
53             =item * cart_class
54              
55             If you wish to subclass the cart you can have L</shop_cart> return your
56             subclassed cart instead. You set the cart class via C<cart_class>.
57             Defaults to L<Dancer2::Plugin::Interchange6::Cart>.
58              
59             =item * carts_var_name
60              
61             The plugin caches carts in a L<Dancer2/var> and the name of the var used can
62             be set via C<carts_var_name>. Defaults to C<ic6_carts>.
63              
64             =back
65              
66             =head1 ROUTES
67              
68             You can use the L<Dancer2::Plugin::Interchange6::Routes> plugin bundled with this
69             plugin to setup standard routes for:
70              
71             =over 4
72              
73             =item product listing
74              
75             =item product display
76              
77             =item cart display
78              
79             =item checkout form
80              
81             =back
82              
83             To enable these routes, you put the C<shop_setup_routes> keyword at the end
84             of your main module:
85              
86             package MyShop;
87              
88             use Dancer2;
89             use Dancer2::Plugin::Interchange6;
90             use Dancer2::Plugin::Interchange6::Routes;
91              
92             get '/shop' => sub {
93             ...
94             };
95              
96             ...
97              
98             shop_setup_routes;
99              
100             true;
101              
102             Please refer to L<Dancer2::Plugin::Interchange6::Routes> for configuration
103             options and further information.
104              
105             =head1 KEYWORDS
106              
107             =head2 shop_cart
108              
109             Returns L<Dancer2::Plugin::Interchange6::Cart> object.
110              
111              
112             =head2 shop_charge
113              
114             Creates payment order and authorizes amount.
115              
116             =head2 shop_redirect
117              
118             Calls L<Interchange6::Schema::ResultSet::UriRedirect/redirect> with given args.
119              
120             =head2 shop_schema
121              
122             Returns L<Interchange6::Schema> object.
123              
124             =head2 shop_...
125              
126             Accessors for L<Interchange6::Schema> result classes. You can use it
127             to retrieve a single object or the corresponding result set.
128              
129             shop_product('F0001')->uri;
130              
131             shop_navigation->search({type => 'manufacturer',
132             active => 1});
133              
134             Available accessors are:
135              
136             =over
137              
138             =item C<shop_address>
139              
140             =item C<shop_attribute>
141              
142             =item C<shop_country>
143              
144             =item C<shop_message>
145              
146             =item C<shop_navigation>
147              
148             =item C<shop_order>
149              
150             =item C<shop_product>
151              
152             =item C<shop_state>
153              
154             =item C<shop_user>
155              
156             =back
157              
158             =head1 HOOKS
159              
160             This plugin installs the following hooks:
161              
162             =head2 Add to cart
163              
164             The functions registered for these hooks receive the cart object
165             and the item to be added as parameters.
166              
167             =over 4
168              
169             =item before_cart_add_validate
170              
171             Triggered before item is validated for adding to the cart.
172              
173             =item before_cart_add
174              
175             Triggered before item is added to the cart.
176              
177             =item after_cart_add
178              
179             Triggered after item is added to the cart.
180             Used by DBI backend to save item to the database.
181              
182             =back
183              
184             =head2 Update cart
185              
186             The functions registered for these hooks receive the cart object,
187             the current item in the cart and the updated item.
188              
189             =over 4
190              
191             =item before_cart_update
192              
193             Triggered before cart item is updated (changing quantity).
194              
195             =item after_cart_update
196              
197             Triggered after cart item is updated (changing quantity).
198             Used by DBI backend to update item to the database.
199              
200             =back
201              
202             =head2 Remove from cart
203              
204             The functions registered for these hooks receive the cart object
205             and the item to be added as parameters.
206              
207             =over 4
208              
209             =item before_cart_remove_validate
210              
211             Triggered before item is validated for removal.
212             Receives cart object and item SKU.
213              
214             =item before_cart_remove
215              
216             Triggered before item is removed from the cart.
217             Receives cart object and item.
218              
219             =item after_cart_remove
220              
221             Triggered after item is removed from the cart.
222             Used by DBI backend to delete item from the database.
223             Receives cart object and item.
224              
225             =back
226              
227             =head2 Clear cart
228              
229             =over 4
230              
231             =item before_cart_clear
232              
233             Triggered before cart is cleared.
234              
235             =item after_cart_clear
236              
237             Triggered after cart is cleared.
238              
239             =back
240              
241             =head2 Rename cart
242              
243             The functions registered for these hooks receive the cart object,
244             the old name and the new name.
245              
246             =over 4
247              
248             =item before_cart_rename
249              
250             Triggered before cart is renamed.
251              
252             =item after_cart_rename
253              
254             Triggered after cart is renamed.
255              
256             =item before_cart_set_users_id
257              
258             Triggered before users_id is set for the cart.
259              
260             =item after_cart_set_users_id
261              
262             Triggered after users_id is set for the cart.
263              
264             =item before_cart_set_sessions_id
265              
266             Triggered before sessions_id is set for the cart.
267              
268             =item after_cart_set_sessions_id
269              
270             Triggered after sessions_id is set for the cart.
271              
272             =back
273              
274             =head1 EXPIRE DBIC SESSIONS
275              
276             This command expires/manages DBIC sessions and carts. NOTE: For proper
277             functionality please copy/link to Dancer2 App/bin directory.
278              
279             interchange6-expire-sessions
280              
281             =cut
282              
283             # config
284              
285             has cart_class => (
286             is => 'ro',
287             from_config => sub { 'Dancer2::Plugin::Interchange6::Cart' },
288             );
289              
290             has carts_var_name => (
291             is => 'ro',
292             from_config => sub { 'ic6_carts' },
293             );
294              
295             # plugins we use
296              
297             has plugin_auth_extensible => (
298             is => 'ro',
299             is => 'lazy',
300             default => sub {
301             $_[0]->app->with_plugin('Dancer2::Plugin::Auth::Extensible');
302             },
303             handles => ['logged_in_user'],
304             init_arg => undef,
305             );
306              
307             has plugin_dbic => (
308             is => 'ro',
309             is => 'lazy',
310             default => sub {
311             $_[0]->app->with_plugin('Dancer2::Plugin::DBIC');
312             },
313             handles => [ 'resultset', 'schema' ],
314             init_arg => undef,
315             );
316              
317             # hooks
318              
319             plugin_hooks(
320             qw/before_cart_add_validate
321             before_cart_add after_cart_add
322             before_cart_update after_cart_update
323             before_cart_remove_validate
324             before_cart_remove after_cart_remove
325             before_cart_rename after_cart_rename
326             before_cart_clear after_cart_clear
327             before_cart_set_users_id after_cart_set_users_id
328             before_cart_set_sessions_id after_cart_set_sessions_id
329             before_cart_display
330             before_checkout_display
331             before_login_display
332             /
333             );
334              
335             plugin_keywords 'shop_address',
336             'shop_attribute',
337             [ 'shop_cart', 'cart' ],
338             'shop_charge',
339             'shop_country',
340             'shop_message',
341             'shop_navigation',
342             'shop_order',
343             'shop_product',
344             'shop_redirect',
345             'shop_schema',
346             'shop_state',
347             'shop_user';
348              
349             sub BUILD {
350 2     2 0 3338 my $plugin = shift;
351 2         7 weaken ( my $weak_plugin = $plugin );
352             $plugin->app->add_hook(
353             Dancer2::Core::Hook->new(
354             name => 'before',
355             code => sub {
356             # D2PAE::Provider::DBIC returns logged_in_user as hashref
357             # instead of a proper user result so we have to mess about.
358             # At some point in the future D2PAE will be fixed to allow
359             # user objects to be returned.
360 119   100 119   1764990 my $user = $weak_plugin->logged_in_user || undef;
361 119 100       545573 if ( $user ) {
362             $user = $weak_plugin->shop_user->find(
363             {
364             username => $user->{username}
365             }
366 32         129 );
367             }
368 119         169690 $weak_plugin->shop_schema->set_current_user($user);
369             },
370             )
371 2         47 );
372             }
373              
374             sub shop_address {
375 3     3 1 18114 shift->_shop_resultset( 'Address', @_ );
376             }
377              
378             sub shop_attribute {
379 3     3 1 10470 shift->_shop_resultset( 'Attribute', @_ );
380             }
381              
382             sub shop_cart {
383 65     65 1 2744 my $plugin = shift;
384              
385 65         104 my %args;
386              
387             # cart name from arg or default 'main'
388 65 100       338 $args{name} = @_ == 1 ? $_[0] : 'main';
389              
390             # set name of var we will stash carts in
391 65         1146 my $var = $plugin->carts_var_name;
392 65         738 $plugin->app->log( "debug", "carts_var_name: $var" );
393              
394 65   100     32464 my $carts = $plugin->app->request->var($var) || {};
395              
396 65 100       963 if ( !defined $carts->{ $args{name} } ) {
397              
398             # can't find this cart in stash
399              
400 63         155 $args{plugin} = $plugin;
401 63         987 $args{schema} = $plugin->schema;
402 63         4535 $args{sessions_id} = $plugin->app->session->id;
403              
404 63 100       2312 if ( my $user_ref = $plugin->logged_in_user ) {
405              
406             # user is logged in
407             # FIXME: D2PAE currently returns a hashref
408 12         837 $args{users_id} = $user_ref->{users_id};
409             }
410              
411 63         4135 $carts->{ $args{name} } = use_module($plugin->cart_class)->new(%args);
412             }
413              
414             # stash carts back in var
415 65         43843 $plugin->app->request->var( $var => $carts );
416              
417 65         865 return $carts->{ $args{name} };
418             }
419              
420             sub shop_charge {
421 6     6 1 1291 my ( $plugin, %args ) = @_;
422 6         12 my ( $schema, $bop_object, $payment_settings, $provider,
423             $provider_settings );
424              
425 6         96 $payment_settings = $plugin->config->{payment};
426              
427 6 50       50 die "No payment setting" unless $payment_settings;
428              
429             # determine payment provider
430 6 100       19 if ( $args{provider} ) {
431 4         6 $provider = $args{provider};
432             }
433             else {
434 2         7 $provider = $payment_settings->{default_provider};
435             }
436              
437 6 100       28 if ( exists $payment_settings->{providers}->{$provider} ) {
438 5         51 $provider_settings = $payment_settings->{providers}->{$provider};
439             }
440             else {
441 1         15 die "Settings for provider $provider missing.";
442             }
443              
444             my %payment_data = (
445             payment_mode => $provider,
446             status => 'request',
447             sessions_id => $plugin->app->session->id,
448             payment_action => 'charge',
449             amount => $args{amount},
450 5         91 users_id => $plugin->app->session->read('logged_in_user_id'),
451             );
452              
453             # create payment order
454 5         286 $schema = $plugin->shop_schema;
455              
456 5         253 my $payment_order =
457             $schema->resultset('PaymentOrder')->create( \%payment_data );
458              
459             # create BOP object wrapper with provider settings
460 4         15132 $bop_object =
461             Dancer2::Plugin::Interchange6::Business::OnlinePayment->new( $provider,
462             %$provider_settings );
463              
464 4         92 $bop_object->payment_order($payment_order);
465              
466             # call charge method
467 4         25 $bop_object->charge(%args);
468              
469 3 100       41 if ( $bop_object->is_success ) {
470 2         30 $payment_order->update(
471             {
472             status => 'success',
473             auth_code => $bop_object->authorization,
474             }
475             );
476             }
477             else {
478 1         12 $payment_order->update(
479             {
480             status => 'failure',
481             payment_error_code => $bop_object->error_code,
482             payment_error_message => $bop_object->error_message,
483             }
484             );
485             }
486              
487 3         7271 return $bop_object;
488             }
489              
490             sub shop_country {
491 4     4 1 8310 shift->_shop_resultset( 'Country', @_ );
492             }
493              
494             sub shop_message {
495 5     5 1 35789 shift->_shop_resultset( 'Message', @_ );
496             }
497              
498             sub shop_navigation {
499 13     13 1 6256 shift->_shop_resultset( 'Navigation', @_ );
500             }
501              
502             sub shop_order {
503 3     3 1 12242 shift->_shop_resultset( 'Order', @_ );
504             }
505              
506             sub shop_product {
507 65     65 1 12360 shift->_shop_resultset( 'Product', @_ );
508             }
509              
510             sub shop_redirect {
511 5     5 1 684 return $_[0]->resultset('UriRedirect')->redirect( $_[1] );
512             }
513              
514             sub shop_schema {
515 158     158 1 183855 my $plugin = shift;
516 158         240 my $schema_key;
517              
518 158 100       421 if (@_) {
519 2         4 $schema_key = $_[0];
520             }
521             else {
522 156         263 $schema_key = 'default';
523             }
524              
525 158         2805 return $plugin->schema($schema_key);
526             }
527              
528             sub shop_state {
529 3     3 1 8859 shift->_shop_resultset( 'State', @_ );
530             }
531              
532             sub shop_user {
533 42     42 1 11538 shift->_shop_resultset( 'User', @_ );
534             }
535              
536             sub _shop_resultset {
537 141     141   212 my $plugin = shift;
538 141         225 my ( $name, $key ) = @_;
539              
540 141 100       445 if ( defined $key ) {
541 34         598 return $plugin->resultset($name)->find($key);
542             }
543              
544 107         1989 return $plugin->resultset($name);
545             }
546              
547             =head1 ACKNOWLEDGEMENTS
548              
549             The L<Dancer2> developers and community for their great application framework
550             and for their quick and competent support.
551              
552             Peter Mottram for his patches and conversion of this plugin to Dancer2.
553              
554             =head1 LICENSE AND COPYRIGHT
555              
556             Copyright 2010-2016 Stefan Hornburg (Racke).
557              
558             This program is free software; you can redistribute it and/or modify it
559             under the terms of either: the GNU General Public License as published
560             by the Free Software Foundation; or the Artistic License.
561              
562             See http://dev.perl.org/licenses/ for more information.
563              
564             =head1 SEE ALSO
565              
566             L<Interchange6>, L<Interchange6::Schema>
567              
568             =cut
569              
570             1;