commit cef8c5bde10f793db0890dcfd18cbf20b0e69c90
Author: Colin Kuskie <colink@perldreamer.com>
Date: Tue May 24 11:52:48 2011 -0700
Build a Shop::Transaction.
commit c386079ac29fc70c1cc63d0c2844511ce9db553e
Author: Colin Kuskie <colink@perldreamer.com>
Date: Tue May 24 11:34:13 2011 -0700
Pull a session out of the object.
commit 59d780eb0ffcc82572491f85f08eb4ac04cab109
Author: Colin Kuskie <colink@perldreamer.com>
Date: Tue May 24 11:15:05 2011 -0700
Rework PayPalStd driver to create transaction before sending the user off to PayPal.
commit cd2683aa8595875f7c501f29c622abaf350e90f6
Author: Colin Kuskie <colink@perldreamer.com>
Date: Tue May 24 11:14:19 2011 -0700
Fix some comments to be more cleanly readable.
commit cf1fecfb916906c4d8ec8d33bd85c59b0aea3b7c
Author: Colin Kuskie <colink@perldreamer.com>
Date: Tue May 24 11:13:52 2011 -0700
Make the transaction optional when displaying a payment error.
commit f3c949e03a18ac513938f2ed483002c5304663d5
Author: Colin Kuskie <colink@perldreamer.com>
Date: Mon May 23 19:19:56 2011 -0700
Remove dead code.
commit 5ddcb49f094fd054f79da38c4a95dd86c55a157f
Author: Colin Kuskie <colink@perldreamer.com>
Date: Thu May 19 11:31:15 2011 -0700
If a transaction is updated with a cart object, remove all transationItems from the transaction before adding new ones.
commit 3315cb30a5c1fd4d583ce352cbc9022e52544404
Author: Colin Kuskie <colink@perldreamer.com>
Date: Thu May 19 11:30:58 2011 -0700
Remove duplicate entries from ExpressCheckout i18n
commit b46d5af528d7223d12ecbed383e798cfc376ad2a
Author: Colin Kuskie <colink@perldreamer.com>
Date: Mon May 23 16:07:33 2011 -0700
Fix the version number of the PayPal API we send. Add the serialized request to the log file. Fix i18n labels so it's clear which username and password to use.
commit 1604d375822eb013c633b72993fa524703a72127
Author: Colin Kuskie <colink@perldreamer.com>
Date: Wed May 18 15:50:42 2011 -0700
Fix copy/paste errors from other i18n modules into ExpressCheckout.
commit f2c958fc7128348a18a005bfbadf83457861d6e2
Author: Colin Kuskie <colink@perldreamer.com>
Date: Wed May 18 11:26:15 2011 -0700
Update out of date POD about checking out.
commit 61ca80b15701733a1a7c7eae5d825b161e0c71c1
Author: Colin Kuskie <colink@perldreamer.com>
Date: Mon May 23 16:05:09 2011 -0700
Fix documentation in appendCartVariables, and return the hash instead of $self.
commit d3b7341c44c924f395f8594c8ae77d8187170c9f
Author: Colin Kuskie <colink@perldreamer.com>
Date: Mon May 23 16:03:20 2011 -0700
Remove variables that were not being used.
commit 2913f96182a7630bce01998bb022d3ebf4842171
Author: Colin Kuskie <colink@perldreamer.com>
Date: Mon May 16 21:05:54 2011 -0700
Pull isRecurring directly from the Cart if creating/updating a transaction from one.
351 lines
9.2 KiB
Perl
351 lines
9.2 KiB
Perl
package WebGUI::Shop::PayDriver::PayPal::ExpressCheckout;
|
|
|
|
=head1 LEGAL
|
|
|
|
-------------------------------------------------------------------
|
|
WebGUI is Copyright 2001-2009 Plain Black Corporation.
|
|
-------------------------------------------------------------------
|
|
Please read the legal notices (docs/legal.txt) and the license
|
|
(docs/license.txt) that came with this distribution before using
|
|
this software.
|
|
-------------------------------------------------------------------
|
|
http://www.plainblack.com info@plainblack.com
|
|
-------------------------------------------------------------------
|
|
|
|
=cut
|
|
|
|
use strict;
|
|
use base qw/WebGUI::Shop::PayDriver/;
|
|
|
|
use LWP::UserAgent;
|
|
use Tie::IxHash;
|
|
use WebGUI::International;
|
|
use WebGUI::Form;
|
|
use URI::Escape;
|
|
use URI::Split;
|
|
use URI;
|
|
use Readonly;
|
|
use Data::Dumper;
|
|
|
|
=head1 NAME
|
|
|
|
WebGUI::Shop::PayDriver::PayPal
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Payment driver that talks to PayPal using the Express Checkout API
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
# in webgui config file...
|
|
|
|
"paymentDrivers" : [
|
|
"WebGUI::Shop::PayDriver::Cash",
|
|
"WebGUI::Shop::PayDriver::PayPal",
|
|
...
|
|
],
|
|
|
|
=head1 METHODS
|
|
|
|
The following methods are available from this class:
|
|
|
|
=cut
|
|
|
|
Readonly my $I18N => 'PayDriver_ExpressCheckout';
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 apiUrl
|
|
|
|
Returns the URL for the PayPal API (or the sandbox, if we are configured to
|
|
use the sandbox)
|
|
|
|
=cut
|
|
|
|
sub apiUrl {
|
|
my $self = shift;
|
|
return $self->get( $self->get('testMode') ? 'apiSandbox' : 'api' );
|
|
}
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 definition
|
|
|
|
Standard definition method.
|
|
|
|
=cut
|
|
|
|
sub definition {
|
|
my ( $class, $session, $definition ) = @_;
|
|
my $i18n = WebGUI::International->new( $session, $I18N );
|
|
|
|
tie my %fields, 'Tie::IxHash';
|
|
my @fieldNames = qw(
|
|
paypal sandbox
|
|
api apiSandbox
|
|
user password
|
|
currency testMode
|
|
signature
|
|
);
|
|
|
|
foreach my $f (@fieldNames) {
|
|
$fields{$f} = {
|
|
fieldType => 'text',
|
|
label => $i18n->get($f),
|
|
hoverHelp => $i18n->get("$f help"),
|
|
};
|
|
}
|
|
|
|
$fields{currency}{defaultValue} = 'USD';
|
|
|
|
$fields{testMode}{fieldType} = 'YesNo';
|
|
|
|
$fields{sandbox}{defaultValue} = 'https://www.sandbox.paypal.com/webscr';
|
|
$fields{apiSandbox}{defaultValue} = 'https://api-3t.sandbox.payPal.com/nvp';
|
|
|
|
$fields{paypal}{defaultValue} = 'https://www.paypal.com/webscr';
|
|
$fields{api}{defaultValue} = 'https://api-3t.payPal.com/nvp';
|
|
|
|
$fields{summaryTemplateId} = {
|
|
fieldType => 'template',
|
|
label => $i18n->get('summary template'),
|
|
hoverHelp => $i18n->get('summary template help'),
|
|
namespace => 'Shop/Credentials',
|
|
defaultValue => 'GqnZPB0gLoZmqQzYFaq7bg',
|
|
},
|
|
|
|
push @{$definition}, {
|
|
name => $i18n->get('name'),
|
|
properties => \%fields,
|
|
};
|
|
|
|
return $class->SUPER::definition( $session, $definition );
|
|
} ## end sub definition
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 getButton
|
|
|
|
Overridden, submits to www_sendToPaypal with the proper parameters.
|
|
|
|
=cut
|
|
|
|
sub getButton {
|
|
my $self = shift;
|
|
my $session = $self->session;
|
|
|
|
my $payForm
|
|
= WebGUI::Form::formHeader($session)
|
|
. $self->getDoFormTags('sendToPayPal')
|
|
. WebGUI::Form::submit( $session, { value => $self->get('name') } )
|
|
. WebGUI::Form::formFooter($session);
|
|
|
|
return $payForm;
|
|
}
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 payPalForm ( %fields )
|
|
|
|
Returns a hashref representing a form (suitable for an LWP post) for talking
|
|
to the PayPal API. Fields can be either name value pairs or a hashref. If it
|
|
is a hashref, it will be modified in place.
|
|
|
|
=cut
|
|
|
|
sub payPalForm {
|
|
my $self = shift;
|
|
my $args = ref $_[0] eq 'HASH' ? shift : {@_};
|
|
$args->{VERSION} = '2.3';
|
|
$args->{USER} = $self->get('user');
|
|
$args->{PWD} = $self->get('password');
|
|
$args->{SIGNATURE} = $self->get('signature');
|
|
|
|
return $args;
|
|
}
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 payPalUrl
|
|
|
|
Returns the URL for the PayPal site (or the sandbox, if we are configured to
|
|
use the sandbox)
|
|
|
|
=cut
|
|
|
|
sub payPalUrl {
|
|
my $self = shift;
|
|
return $self->get( $self->get('testMode') ? 'sandbox' : 'paypal' );
|
|
}
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 processPayment ( transaction )
|
|
|
|
Implements the interface defined in WebGUI::Shop::PayDriver. Notably, on
|
|
error 'message' will be an HTML table representing the parameters that the
|
|
PayPal API spit back.
|
|
|
|
=cut
|
|
|
|
sub processPayment {
|
|
my ( $self, $transaction ) = @_;
|
|
|
|
my $form = $self->payPalForm(
|
|
METHOD => 'DoExpressCheckoutPayment',
|
|
PAYERID => $self->session->form->process('PayerId'),
|
|
TOKEN => $self->session->form->process('token'),
|
|
AMT => $self->getCart->calculateTotal,
|
|
CURRENCYCODE => $self->get('currency'),
|
|
PAYMENTACTION => 'SALE',
|
|
);
|
|
my $response = LWP::UserAgent->new->post( $self->apiUrl, $form );
|
|
my $params = $self->responseHash($response);
|
|
if ($params) {
|
|
if ( $params->{ACK} !~ /^Success/ ) {
|
|
my $status = $params->{ACK};
|
|
my $message = '<table><tr><th>Field</th><th>Value</th></tr>';
|
|
foreach my $k ( keys %$params ) {
|
|
$message .= "<tr><td>$k</td><td>$params->{$k}</td></tr>";
|
|
}
|
|
$message .= '</table>';
|
|
return ( 0, undef, $status, $message );
|
|
}
|
|
|
|
my $status = $params->{PAYMENTSTATUS};
|
|
|
|
my $i18n = WebGUI::International->new( $self->session, $I18N );
|
|
my $message = sprintf $i18n->get('payment status'), $status;
|
|
return ( 1, $params->{TRANSACTIONID}, $status, $message );
|
|
}
|
|
|
|
return ( 0, undef, $response->status_code, $response->status_line );
|
|
} ## end sub processPayment
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 responseHash (response)
|
|
|
|
Chops up the body of a paypal response into a hashref (or undef if the request
|
|
failed)
|
|
|
|
=cut
|
|
|
|
sub responseHash {
|
|
my ( $self, $response ) = @_;
|
|
return undef unless $response->is_success;
|
|
local $_ = uri_unescape( $response->content );
|
|
return { map { split /=/ } split /[&;]/ };
|
|
}
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 www_payPalCallback
|
|
|
|
Handler that PayPal redirects to once payment has been confirmed on their end
|
|
|
|
=cut
|
|
|
|
sub www_payPalCallback {
|
|
my $self = shift;
|
|
|
|
my $transaction = $self->processTransaction;
|
|
|
|
return $transaction->get('isSuccessful')
|
|
? $transaction->thankYou
|
|
: $self->displayPaymentError($transaction);
|
|
}
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 www_sendToPayPal
|
|
|
|
Sets up payPal transaction and redirects the user off to payPal land
|
|
|
|
=cut
|
|
|
|
sub www_sendToPayPal {
|
|
my $self = shift;
|
|
my $session = $self->session;
|
|
my $url = $session->url;
|
|
my $base = $url->getSiteURL . $url->page;
|
|
|
|
my $returnUrl = URI->new($base);
|
|
$returnUrl->query_form( {
|
|
shop => 'pay',
|
|
method => 'do',
|
|
do => 'payPalCallback',
|
|
paymentGatewayId => $self->getId,
|
|
}
|
|
);
|
|
|
|
my $cancelUrl = URI->new($base);
|
|
$cancelUrl->query_form( { shop => 'cart' } );
|
|
|
|
my $form = $self->payPalForm(
|
|
METHOD => 'SetExpressCheckout',
|
|
AMT => $self->getCart->calculateTotal,
|
|
CURRENCYCODE => $self->get('currency'),
|
|
RETURNURL => $returnUrl->as_string,
|
|
CANCELURL => $cancelUrl->as_string,
|
|
PAYMENTACTION => 'SALE',
|
|
);
|
|
|
|
my $testMode = $self->get('testMode');
|
|
my $response = LWP::UserAgent->new->post( $self->apiUrl, $form );
|
|
my $params = $self->responseHash($response);
|
|
my $i18n = WebGUI::International->new( $self->session, $I18N );
|
|
my $error;
|
|
|
|
if ($params) {
|
|
unless ( $params->{ACK} =~ /^Success/ ) {
|
|
my $log = sprintf "Paypal error: Request/response below: %s\n%s\n", Dumper($form), Dumper($params);
|
|
$log .= $response->request->as_string;
|
|
$session->log->error($log);
|
|
$error = $i18n->get('internal paypal error');
|
|
}
|
|
}
|
|
else {
|
|
$error = $response->status_line;
|
|
}
|
|
|
|
if ($error) {
|
|
my $message = sprintf $i18n->get('api error'), $error;
|
|
return $session->style->userStyle($message);
|
|
}
|
|
|
|
my $dest = URI->new( $self->payPalUrl );
|
|
$dest->query_form( {
|
|
cmd => '_express-checkout',
|
|
token => $params->{TOKEN},
|
|
}
|
|
);
|
|
|
|
return $session->http->setRedirect($dest);
|
|
} ## end sub www_sendToPayPal
|
|
|
|
=head1 LIMITATIONS
|
|
|
|
=over 4
|
|
|
|
=item
|
|
|
|
Doesn't handle recurring payments, although Paypal can do that.
|
|
|
|
=item
|
|
|
|
There is no itemization of the cart for Paypal's records, just one total
|
|
(could do taxes, shipping, each item as separate things).
|
|
|
|
=item
|
|
|
|
Paypal's shipping information is ignored; this could be changed to accept new
|
|
shipping info from PayPal, but that's somewhat fragile. We're currently just
|
|
pretending PayPal is a payment gateway.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
1;
|
|
|