File Coverage

blib/lib/Catalyst/Model/PayPal/IPN.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Catalyst::Model::PayPal::IPN;
2              
3 2     2   63020 use Moose;
  0            
  0            
4             use Business::PayPal::IPN;
5             use namespace::clean -except => ['meta'];
6              
7             our $VERSION = '0.04';
8             our $AUTHORITY = 'cpan:MSTROUT';
9              
10             extends 'Catalyst::Model';
11              
12             has 'req' => (
13             is => 'rw',
14             required => 1,
15             lazy => 1,
16             default => sub { confess "req not provided before use" }
17             );
18              
19             has 'business_email' => (
20             is => 'rw',
21             required => 1,
22             lazy => 1,
23             default => sub { confess "business_email not provided before use" }
24             );
25              
26             has 'currency_code' => (
27             is => 'rw',
28             required => 1,
29             lazy => 1,
30             default => sub { confess "currency_code not provided before use" }
31             );
32              
33             has 'postback_action' => (
34             is => 'rw',
35             required => 1,
36             lazy => 1,
37             default => sub { confess "postback_action not provided before use" }
38             );
39              
40             has 'postback_url' => (
41             is => 'rw',
42             required => 1,
43             lazy => 1,
44             default => sub { confess "postback_url not provided before use" }
45             );
46              
47             has 'cancellation_action' => (
48             is => 'rw',
49             required => 1,
50             lazy => 1,
51             default => sub { confess "cancellation_action not provided before use" }
52             );
53              
54             has 'cancellation_url' => (
55             is => 'rw',
56             required => 1,
57             lazy => 1,
58             default => sub { confess "cancellation_url not provided before use" }
59             );
60              
61             has 'completion_action' => (
62             is => 'rw',
63             required => 1,
64             lazy => 1,
65             default => sub { confess "completion_action not provided before use" }
66             );
67              
68             has 'completion_url' => (
69             is => 'rw',
70             required => 1,
71             lazy => 1,
72             default => sub { confess "completion_url not provided before use" }
73             );
74              
75             has 'debug_mode' => ( is => 'rw', required => 1, default => sub { 0 } );
76              
77             has 'encrypt_mode' => ( is => 'rw', required => 1, default => sub { 0 },
78             trigger => sub {shift->_check_encrypt_mode} );
79              
80             has 'cert' => (
81             is => 'rw',
82             required => 0,
83             lazy => 1,
84             default =>
85             sub { confess "cert not provided for encryption" if shift->encrypt_mode }
86             );
87              
88             has 'cert_key' => (
89             is => 'rw',
90             required => 0,
91             lazy => 1,
92             default => sub {
93             confess "cert_key not provided for encryption" if shift->encrypt_mode;
94             }
95             );
96              
97             has 'paypal_cert' => (
98             is => 'rw',
99             required => 0,
100             lazy => 1,
101             default => sub {
102             confess "paypal_cert not provided for encryption"
103             if shift->encrypt_mode;
104             }
105             );
106              
107             has 'paypal_gateway' => (
108             is => 'rw',
109             required => 1,
110             lazy => 1,
111             default => sub { shift->build_paypal_gateway },
112             );
113              
114             has '_ipn_object' => (
115             is => 'ro',
116             required => 1,
117             lazy => 1,
118             default => sub { shift->_build_ipn_object },
119             );
120              
121             sub BUILD {
122             shift->_check_encrypt_mode;
123             }
124              
125             sub _check_encrypt_mode {
126             Catalyst::Utils::ensure_class_loaded('Business::PayPal::EWP')
127             if shift->encrypt_mode;
128             }
129              
130             sub ACCEPT_CONTEXT {
131             my ( $self, $c ) = @_;
132             return $c->stash->{ ref($self) } ||= $self->_build_context_copy($c);
133             }
134              
135             sub _build_context_copy {
136             my ( $self, $c ) = @_;
137             my $copy = bless( {%$self}, ref($self) );
138             my $req = $c->req;
139             $copy->req($req);
140             $copy->_fill_action( $c, $_ ) for qw/postback completion cancellation/;
141             return $copy;
142             }
143              
144             sub _fill_action {
145             my ( $self, $c, $fill ) = @_;
146             my $url_meth = "${fill}_url";
147             my $args_meth = "${fill}_action";
148             my @args = @{ $self->$args_meth };
149             my ( $controller, $action_name ) = ( shift(@args), shift(@args) );
150             my $action = $c->controller($controller)->action_for($action_name);
151             my $uri = $c->uri_for( $action => @args );
152             $self->$url_meth("${uri}");
153             }
154              
155             sub build_paypal_gateway {
156             my $self = shift;
157             return (
158             $self->debug_mode
159             ? 'https://www.sandbox.paypal.com/cgi-bin/webscr'
160             : 'https://www.paypal.com/cgi-bin/webscr'
161             );
162             }
163              
164             sub _build_ipn_object {
165             my $self = shift;
166             local $Business::PayPal::IPN::GTW = $self->paypal_gateway;
167             my $ipn = Business::PayPal::IPN->new( query => $self->req );
168             unless ($ipn) {
169             $ipn =
170             Catalyst::Model::PayPal::IPN::ErrorHandle->new(
171             error => Business::PayPal::IPN->error );
172             }
173             return $ipn;
174             }
175              
176             sub is_completed {
177             my $self = shift;
178             return $self->_ipn_object->completed;
179             }
180              
181             sub error {
182             my $self = shift;
183             return
184             unless $self->_ipn_object->isa(
185             'Catalyst::Model::PayPal::IPN::ErrorHandle');
186             return $self->_ipn_object->error;
187             }
188              
189             # https://www.paypal.com/IntegrationCenter/ic_ipn-pdt-variable-reference.html
190              
191             sub buyer_info {
192             my $self = shift;
193             return unless $self->is_completed;
194             return $self->_ipn_object->vars();
195             }
196              
197             sub correlation_info {
198             my $self = shift;
199             return {
200             amount => $self->req->params->{mc_gross},
201             map { ( $_ => $self->req->params->{$_} ) } qw/invoice custom/
202             };
203             }
204              
205             sub form_info {
206             my ( $self, $args ) = @_;
207             foreach my $key (qw/amount item_name/) {
208             confess "${key} must be defined" unless defined( $args->{$key} );
209             }
210             return {
211             business => $self->business_email,
212             currency_code => $self->currency_code,
213             notify_url => $self->postback_url,
214             return => $self->completion_url,
215             cancel_return => $self->cancellation_url,
216             cmd => '_ext-enter',
217             redirect_cmd => '_xclick',
218             %$args
219             };
220             }
221              
222             sub encrypt_form {
223             my ( $self, $args ) = @_;
224              
225             confess "encrypt_mode must be enabled" unless $self->encrypt_mode;
226              
227             my $form_args = $self->form_info($args);
228              
229             # SignAndEncrypt needs CSV key/vals
230             my $form;
231             for my $form_param ( keys %$form_args ) {
232             $form .= $form_param . '=' . $form_args->{$form_param} . "\n";
233             }
234              
235             return Business::PayPal::EWP::SignAndEncrypt(
236             $form, $self->cert_key, $self->cert, $self->paypal_cert
237             );
238             }
239              
240             __PACKAGE__->meta->make_immutable;
241              
242             package Catalyst::Model::PayPal::IPN::ErrorHandle;
243              
244             use Moose;
245             use namespace::clean -except => ['meta'];
246              
247             has 'error' => ( is => 'ro', required => 1 );
248              
249             sub completed { 0 }
250              
251             __PACKAGE__->meta->make_immutable;
252              
253             1;
254             __END__
255              
256             =encoding utf8
257              
258             =head1 NAME
259              
260             Catalyst::Model::PayPal::IPN - Handle Instant Payment Notifications and PayPal Button Generation
261              
262             =head1 VERSION
263              
264             This document describes Catalyst::Model::PayPal::IPN version 0.04
265              
266             =head1 SYNOPSIS
267              
268             lib/MyApp/Model/Paypal/IPN.pm
269              
270             package MyApp::Model::Paypal::IPN;
271              
272             use strict;
273             use warnings;
274             use parent 'Catalyst::Model::PayPal::IPN';
275              
276             1;
277              
278             myapp.yml
279              
280             paypal:
281             cert_id: 3TFC4UDJER95J
282             page_style: MyApp
283             no_note: 1
284             no_shipping: 1
285             lc: GB
286             bn: PP-BuyNowBF
287              
288             Model::Paypal::IPN:
289             debug_mode: 1
290             encrypt_mode: 0
291             business_email: ghenry_1188297224_biz@suretecsystems.com
292             currency_code: GBP
293             cert: /home/ghenry/MyApp/root/auth/paypal_certs/www.myapp.net.crt
294             cert_key: /home/ghenry/MyApp/root/auth/paypal_certs/www.myapp.net.key
295             paypal_cert: /home/ghenry/MyApp/root/auth/paypal_certs/paypal_sandbox_cert.pem
296             completion_action:
297             - Subscribe
298             - subscribe
299             - payment
300             - received
301             postback_action:
302             - Subscribe
303             - subscribe
304             - payment
305             - ipn
306             cancellation_action:
307             - Subscribe
308             - subscribe
309             - payment
310             - cancelled
311              
312             MyApp::Controller::Subscribe
313              
314             =head2 ipn
315              
316             Handle PayPal IPN stuff
317              
318             =cut
319              
320             sub ipn : Path('payment/ipn') {
321             my ( $self, $c ) = @_;
322              
323             my $ipn = $c->model('Paypal::IPN');
324              
325             if ( $ipn->is_completed ) {
326             my %ipn_vars = $ipn->buyer_info();
327             $c->stash->{ipn_vars} = \%ipn_vars;
328              
329             Do stuff here
330              
331             # Just so we reply with something, which in turn sends a HTTP Status 200
332             # OK, which we need to stop PayPal.
333             # We don't get as we don't use a template and RenderView looks for a
334             # template, a body or status equal to 3XX
335             $c->res->body('ok');
336             }
337             else {
338              
339             # Just so we reply with something, which in turn sends a HTTP Status 200
340             # OK, which we need to stop PayPal.
341             # We don't get as we don't use a template and RenderView looks for a
342             # template, a body or status equal to 3XX
343             $c->res->body('not_ok');
344             $c->log->debug( $record_payment_result->transmsgtext ) if $c->debug;
345             $c->log->debug( $ipn->error ) if $ipn->error && $c->debug;
346             }
347             }
348              
349             =head2 cancelled
350              
351             Cancelled Payment
352              
353             =cut
354              
355             sub cancelled : Path('payment/cancelled') {
356             my ( $self, $c ) = @_;
357              
358             Do stuff on cancel
359              
360             $c->stash->{template} = 'user/subscribe/cancelled.tt';
361             }
362              
363             =head2 generate_paypal_buttons
364              
365             =cut
366              
367             sub generate_paypal_buttons : Private {
368             my ( $self, $c ) = @_;
369              
370             if ( $c->stash->{all_buttons} ) {
371             $c->stash->{subtypes} = [
372             $c->model('FTMAdminDB::FTMTariffs')->search(
373             {
374             objectname => 'FTM_SUB_TARIFFS',
375             objectitem => 'TARIFFTYPENO',
376             lovlangid => $langid,
377             },
378             )
379             ];
380              
381             for my $tariff ( @{ $c->stash->{subtypes} } ) {
382             next if $tariff->tariffid == 1;
383             my %data = (
384             #cert_id => $c->config->{paypal}->{cert_id},
385             cmd => '_xclick',
386             item_name => $tariff->itemdesc,
387             item_number => $tariff->tariffid,
388             amount => $tariff->peruser,
389             page_style => $c->config->{paypal}->{page_style},
390             no_shipping => $c->config->{paypal}->{no_shipping},
391             no_note => $c->config->{paypal}->{no_note},
392             'lc' => $c->config->{paypal}->{lc},
393             bn => $c->config->{paypal}->{bn},
394             custom => $c->req->param('subid'),
395             );
396              
397             if ( $c->debug ) {
398             for my $param ( keys %data ) {
399             $c->log->debug( $param . '=' . $data{$param} );
400             }
401             }
402             $c->stash->{unencrypted_form_data} =
403             $c->model('Paypal::IPN')->form_info( \%data );
404              
405             my @button_info = (
406             $tariff->itemdesc, $tariff->peruser,
407             $c->stash->{unencrypted_form_data}
408             );
409             push @{ $c->stash->{unencrypted_buttons} }, \@button_info;
410              
411             #$c->stash->{encrypted_form_data} =
412             # $c->model('Paypal::IPN')->encrypt_form( \%data );
413              
414             #my @button_info = (
415             # $tariff->itemdesc, $tariff->peruser,
416             # $c->stash->{encrypted_form_data}
417             #);
418             #push @{ $c->stash->{encrypted_buttons} }, \@button_info;
419             }
420             }
421             }
422              
423             buttons.tt
424              
425             <table>
426             [% FOREACH button IN unencrypted_buttons %]
427             <tr>
428             <td><b>[% button.0 %]</b></td>
429             <td><b>Price:</b> £[% button.1 %]</td>
430             <td class="content">
431             <form method="post" action="[% c.model('Paypal::IPN').paypal_gateway %]">
432             <input type="hidden" name="cmd" value="_xclick">
433             <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-but23.gif" border="0"
434             name="submit" alt="Make payments with PayPal - it's fast, free and secure!">
435             <img alt="" border="0" src="https://www.paypal.com/en_GB/i/scr/pixel.gif" width="1" height="1">
436             [% FOREACH key IN button.2.keys %]
437             <input type="hidden" name="[% key %]" value="[% button.2.$key %]" />
438             [% END %]
439             </form>
440             </td>
441             </tr>
442             [% END %]
443             <tr>
444             <td class="content">
445             <input type="button" onclick="dojo.widget.byId('profdiag').hide();" value="Close" name="close"/>
446             </td>
447             <td class="content" colspan="2">
448             <a href="#" onclick="javascript:window.open('https://www.paypal.com/uk/cgi-bin/webscr?cmd=xpt/cps/popup/OLCWhatIsPayPal-outside','olcwhatispaypal','toolbar=no,location=no, directories=no, status=no, menubar=no, scrollbars=yes,resizable=yes, width=400, height=350');"><img src="https://www.paypal.com/en_GB/i/bnr/horizontal_solution_PP.gif" border="0"
449             alt="Solution Graphics"></a>
450             </td>
451             </tr>
452             </table>
453              
454              
455             =head1 DESCRIPTION
456              
457             This model handles all the latest PayPal IPN vars, and provides an
458             easy method for checking that the transaction was successful.
459              
460             There are also convenience methods for generating encrypted and non-encrypted
461             PayPal forms and buttons.
462              
463             See L<Business::PayPal::IPN> for more info.
464              
465             B<WARNING:> this module does not have real tests yet, if you encounter problems
466             please report them via L<http://rt.cpan.org/> .
467              
468             =head1 INTERFACE
469              
470             =head2 build_paypal_gateway
471              
472             If debug_mode is on, returns sandbox url, otherwise normal PayPal gateway
473              
474             =head2 is_completed
475              
476             Calls is_completed from L<Business::PayPal::IPN>
477              
478             =head2 error
479              
480             Calls error from L<Business::PayPal::IPN>
481              
482             =head2 buyer_info
483              
484             Returns IPN vars via L<Business::PayPal::IPN>
485              
486             See L<https://www.paypal.com/IntegrationCenter/ic_ipn-pdt-variable-reference.html>
487              
488             =head2 correlation_info
489              
490             Returns a hashref of amount, invoice and custom.
491              
492             =head2 form_info
493              
494             Takes a hashref and returns form data for looping through to create your form.
495              
496             See L<SYNOPSIS>
497              
498             =head2 encrypt_form
499              
500             Encrypts form data.
501              
502             $c->model('Paypal::IPN')->encrypt_form( \%data );
503              
504             =head1 CONFIGURATION AND ENVIRONMENT
505              
506             The usual techniques for suppling model configuration data in Catalyst apply,
507             but the follow should be present:
508              
509             Model::Paypal::IPN:
510             debug_mode: 1
511             encrypt_mode: 0
512             business_email: ghenry_1188297224_biz@suretecsystems.com
513             currency_code: GBP
514             completion_action:
515             - Subscribe
516             - subscribe
517             - payment
518             - received
519             postback_action:
520             - Subscribe
521             - subscribe
522             - payment
523             - ipn
524             cancellation_action:
525             - Subscribe
526             - subscribe
527             - payment
528             - cancelled
529              
530             debug_mode switches form url to the PayPal sandbox url. If using encrypted
531             buttons, i.e.
532              
533             encrypt_mode: 1
534              
535             then the following will be needed:
536              
537             cert: /home/ghenry/MyApp/root/auth/paypal_certs/www.myapp.net.crt
538             cert_key: /home/ghenry/MyApp/root/auth/paypal_certs/www.myapp.net.key
539             paypal_cert: /home/ghenry/MyApp/root/auth/paypal_certs/paypal_sandbox_cert.pem
540              
541             Catalyst::Model::PayPal::IPN requires:
542              
543             =head1 DEPENDENCIES
544              
545             L<Moose>
546              
547             L<namespace::clean>
548              
549             L<Business::PayPal::IPN>
550              
551             L<Business::PayPal::EWP>
552              
553             =head1 BUGS AND LIMITATIONS
554              
555             No bugs have been reported.
556              
557             Please report any bugs or feature requests to
558             C<bug-catalyst-model-paypal-ipn@rt.cpan.org>, or through the web interface at
559             L<http://rt.cpan.org>.
560              
561             =head1 AUTHOR
562              
563             Matt S Trout C<mst@shadowcatsystems.co.uk>
564              
565             Gavin Henry C<ghenry@suretecsystems.com>
566              
567             =head1 LICENCE AND COPYRIGHT
568              
569             Copyright (c) 2007, Matt S Trout, C<mst@shadowcatsystems.co.uk>. All rights reserved.
570              
571             Copyright (c) 2007, Gavin Henry C<ghenry@suretecsystems.com>. All rights reserved.
572              
573             This module is free software; you can redistribute it and/or
574             modify it under the same terms as Perl itself. See L<perlartistic>.
575              
576             =head1 DISCLAIMER OF WARRANTY
577              
578             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
579             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
580             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
581             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
582             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
583             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
584             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
585             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
586             NECESSARY SERVICING, REPAIR, OR CORRECTION.
587              
588             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
589             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
590             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
591             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
592             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
593             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
594             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
595             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
596             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
597             SUCH DAMAGES.
598              
599             =cut