Adding Ogone payment plugin
This commit is contained in:
parent
1f3b382208
commit
489313a0b9
7 changed files with 1307 additions and 10 deletions
565
lib/WebGUI/Shop/PayDriver/Ogone.pm
Normal file
565
lib/WebGUI/Shop/PayDriver/Ogone.pm
Normal file
|
|
@ -0,0 +1,565 @@
|
|||
package WebGUI::Shop::PayDriver::Ogone;
|
||||
|
||||
use strict;
|
||||
|
||||
use WebGUI::Shop::PayDriver;
|
||||
use WebGUI::Exception;
|
||||
use Digest::SHA qw{ sha1_hex };
|
||||
use WebGUI::International;
|
||||
use Data::Dumper;
|
||||
|
||||
use base qw{ WebGUI::Shop::PayDriver };
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 canCheckOutCart ( )
|
||||
|
||||
Returns whether the cart can be checked out by this plugin.
|
||||
|
||||
=cut
|
||||
|
||||
sub canCheckoutCart {
|
||||
my $self = shift;
|
||||
my $cart = $self->getCart;
|
||||
|
||||
return 0 unless $cart->readyForCheckout;
|
||||
return 0 if $cart->requiresRecurringPayment;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 definition ( session, definition )
|
||||
|
||||
See WebGUI::Shop::PayDriver->definition.
|
||||
|
||||
=cut
|
||||
|
||||
sub definition {
|
||||
my $class = shift;
|
||||
my $session = shift;
|
||||
my $definition = shift;
|
||||
|
||||
WebGUI::Error::InvalidParam->throw( error => q{Must provide a session variable} )
|
||||
unless $session && ref $session eq 'WebGUI::Session';
|
||||
|
||||
my $i18n = WebGUI::International->new($session, 'PayDriver_Ogone');
|
||||
|
||||
tie my %fields, 'Tie::IxHash';
|
||||
|
||||
#TODO: internationilazation
|
||||
%fields = (
|
||||
pspid => {
|
||||
fieldType => 'text',
|
||||
label => $i18n->get('psp id'),
|
||||
hoverHelp => $i18n->get('psp id help'),
|
||||
defaultValue => '',
|
||||
},
|
||||
shaSecret => {
|
||||
fieldType => 'password',
|
||||
label => $i18n->get('sha secret'),
|
||||
hoverHelp => $i18n->get('sha secret help'),
|
||||
},
|
||||
postbackSecret => {
|
||||
fieldType => 'password',
|
||||
label => $i18n->get('postback secret'),
|
||||
hoverHelp => $i18n->get('postback secret help'),
|
||||
},
|
||||
locale => {
|
||||
fieldType => 'text',
|
||||
label => $i18n->get('locale'),
|
||||
hoverHelp => $i18n->get('locale help'),
|
||||
defaultValue => 'en_US',
|
||||
maxlength => 5,
|
||||
size => 5,
|
||||
},
|
||||
currency => {
|
||||
fieldType => 'text',
|
||||
label => $i18n->get('currency'),
|
||||
hoverHelp => $i18n->get('currency help'),
|
||||
defaultValue => 'EUR',
|
||||
maxlength => 3,
|
||||
size => 3,
|
||||
},
|
||||
useTestMode => {
|
||||
fieldType => 'yesNo',
|
||||
label => $i18n->get('use test mode'),
|
||||
hoverHelp => $i18n->get('use test mode help'),
|
||||
defaultValue => 1,
|
||||
},
|
||||
);
|
||||
|
||||
push @{ $definition }, {
|
||||
name => $i18n->get('Ogone'),
|
||||
properties => \%fields,
|
||||
};
|
||||
|
||||
return $class->SUPER::definition($session, $definition);
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 getButton ( )
|
||||
|
||||
Returns the HTML for a form containing a button that, when clicked, will take the user to the checkout screen of
|
||||
this plugin.
|
||||
|
||||
=cut
|
||||
|
||||
sub getButton {
|
||||
my $self = shift;
|
||||
my $session = $self->session;
|
||||
my $i18n = WebGUI::International->new($session, 'PayDriver_Ogone');
|
||||
|
||||
my $payForm = WebGUI::Form::formHeader($session)
|
||||
. $self->getDoFormTags('getCredentials')
|
||||
. WebGUI::Form::submit($session, {value => $i18n->get('Ogone') })
|
||||
. WebGUI::Form::formFooter($session);
|
||||
|
||||
return $payForm;
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 getCart
|
||||
|
||||
Returns the cart for either the current user or the transaction passed back by Ogone.
|
||||
|
||||
=cut
|
||||
|
||||
sub getCart {
|
||||
my $self = shift;
|
||||
my $cart;
|
||||
|
||||
if ($self->{_cartId}) {
|
||||
$cart = WebGUI::Shop::Cart->new( $self->session, $self->{_cartId} );
|
||||
}
|
||||
|
||||
return $cart || $self->SUPER::getCart;
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 processPayment ()
|
||||
|
||||
See WebGUI::Shop::PayDriver->processPayment
|
||||
|
||||
=cut
|
||||
|
||||
sub processPayment {
|
||||
my $self = shift;
|
||||
# Since we'll have to create a transaction before doing the actual tranasction, we let it fail
|
||||
# initially with a message that it is pending.
|
||||
# Unless the transaction result with _setPaymentStatus the transaction will fail.
|
||||
|
||||
my $success = $self->{_transactionSuccessful} || 0;
|
||||
my $id = $self->{_ogoneId} || undef;
|
||||
my $status = $self->{_statusCode} || undef;
|
||||
my $message = $self->{_statusMessage} || 'Waiting for checkout';
|
||||
|
||||
return ( $success, $id, $status, $message );
|
||||
return (0, undef, 1, 'Pending');
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 ogoneCheckoutButton ( transaction, address )
|
||||
|
||||
Generates a form with a submit button that, when clicked, posts the payment data for the given transaction to Ogone
|
||||
and takes the user there.
|
||||
|
||||
=head3 transaction
|
||||
|
||||
The instanciated transaction that should be paid.
|
||||
|
||||
=head3 address
|
||||
|
||||
An instanciated WebGUI::Shop::Address object that contains the billing address.
|
||||
|
||||
=cut
|
||||
|
||||
sub ogoneCheckoutButton {
|
||||
my $self = shift;
|
||||
my $transaction = shift;
|
||||
my $address = shift;
|
||||
my $session = $self->session;
|
||||
my $i18n = WebGUI::International->new( $session, 'PayDriver_Ogone' );
|
||||
|
||||
$self->{ _ogoneTransaction } = "done" ;
|
||||
|
||||
# Ogone needs the transaction amount in cents
|
||||
my $amount = $transaction->get('amount') * 100;
|
||||
$amount =~ s/[^\d]//g; # Remove any character from amount except digits.
|
||||
|
||||
my $orderId = $transaction->getId;
|
||||
my $description = "Transaction ID: $orderId";
|
||||
my $pspId = $self->get('pspid');
|
||||
my $name = join " ", $address->get( 'firstName' ), $address->get( 'lastName' );
|
||||
my $email = $address->get('email');
|
||||
|
||||
my $currency = $self->get('currency');
|
||||
|
||||
# Generate sha signature the payment data
|
||||
my $passphrase = join '', $orderId, $amount, $currency, $pspId, $self->get('shaSecret');
|
||||
my $shaSignature = uc sha1_hex( $passphrase );
|
||||
|
||||
# Define the data to be sent to ogone
|
||||
my %parameters = (
|
||||
PSPID => $pspId,
|
||||
orderID => $orderId,
|
||||
amount => $amount,
|
||||
currency => $currency,
|
||||
language => $self->get('locale'),
|
||||
CN => join( " ", $address->get('firstName'), $address->get('lastName') ),
|
||||
EMAIL => $email,
|
||||
ownerZIP => $address->get( 'code' ),
|
||||
owneraddress => join( " ", $address->get('address1'), $address->get('address2'), $address->get('address3') ),
|
||||
ownercty => $address->get('country'),
|
||||
ownertown => $address->get('city'),
|
||||
ownertelno => $address->get('phoneNumber'),
|
||||
COMPLUS => $self->getCart->getId,
|
||||
COM => $description,
|
||||
SHASign => $shaSignature,
|
||||
accepturl =>
|
||||
$self->session->url->getSiteURL.'/?shop=pay&method=do&do=acceptTransaction&paymentGatewayId='.$self->getId,
|
||||
cancelurl =>
|
||||
$self->session->url->getSiteURL.'/?shop=pay&method=do&do=cancelTransaction&paymentGatewayId='.$self->getId,
|
||||
declineurl =>
|
||||
$self->session->url->getSiteURL.'/?shop=pay&method=do&do=declineTransaction&paymentGatewayId='.$self->getId,
|
||||
exceptionurl =>
|
||||
$self->session->url->getSiteURL.'/?shop=pay&method=do&do=exceptionTransaction&paymentGatewayId='.$self->getId
|
||||
);
|
||||
|
||||
# Convert payment data to hidden input tags
|
||||
my $formFields =
|
||||
join "\n",
|
||||
map { WebGUI::Form::hidden( $session, { name => $_, value => $parameters{ $_ } } ) }
|
||||
keys %parameters;
|
||||
|
||||
# Construct actual checkout form
|
||||
|
||||
my $action = $self->get('useTestMode')
|
||||
? 'https://secure.ogone.com/ncol/test/orderstandard.asp'
|
||||
: 'https://secure.ogone.com/ncol/prod/orderstandard.asp'
|
||||
;
|
||||
|
||||
my $form =
|
||||
WebGUI::Form::formHeader( $session, {
|
||||
action => $action,
|
||||
method => 'POST',
|
||||
enctype => 'application/x-www-form-urlencoded',
|
||||
} )
|
||||
. $formFields
|
||||
. WebGUI::Form::submit( $session, { name => 'submit2', value => $i18n->get('pay') } )
|
||||
. WebGUI::Form::formFooter( $session );
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 www_getCredentials ( [ addressId ] )
|
||||
|
||||
Displays the checkout form for this plugin.
|
||||
|
||||
=head3 addressId
|
||||
|
||||
Optionally supply this variable which will set the payment address to this addressId.
|
||||
|
||||
=cut
|
||||
|
||||
sub www_getCredentials {
|
||||
my ($self, $addressId) = @_;
|
||||
my $session = $self->session;
|
||||
my $i18n = WebGUI::International->new( $session, 'PayDriver_Ogone' );
|
||||
|
||||
# Process address from address book if passed
|
||||
$addressId = $session->form->process( 'addressId' );
|
||||
my $address;
|
||||
if ( $addressId ) {
|
||||
$address = eval{ $self->getAddress( $addressId ) };
|
||||
}
|
||||
else {
|
||||
$address = $self->getCart->getShippingAddress;
|
||||
}
|
||||
my $billingAddressHtml = $address->getHtmlFormatted;
|
||||
|
||||
# Fetch transaction
|
||||
my $transactionId = $session->form->process('transactionId');
|
||||
my $transaction;
|
||||
if ($transactionId) {
|
||||
$transaction = WebGUI::Shop::Transaction->new( $session, $transactionId );
|
||||
}
|
||||
|
||||
# Or generate a new one
|
||||
unless ($transaction) {
|
||||
$transaction = $self->processTransaction( $address );
|
||||
}
|
||||
|
||||
# Set the billing address
|
||||
$transaction->update( {
|
||||
paymentAddress => $address,
|
||||
} );
|
||||
|
||||
# Generate the json string that defines where the address book posts the selected address
|
||||
my $callbackParams = {
|
||||
url => $session->url->page,
|
||||
params => [
|
||||
{ name => 'shop', value => 'pay' },
|
||||
{ name => 'method', value => 'do' },
|
||||
{ name => 'do', value => 'getCredentials' },
|
||||
{ name => 'paymentGatewayId', value => $self->getId },
|
||||
],
|
||||
};
|
||||
my $callbackJson = JSON::to_json( $callbackParams );
|
||||
|
||||
# Generate 'Choose billing address' button
|
||||
my $addressButton = WebGUI::Form::formHeader( $session )
|
||||
. WebGUI::Form::hidden( $session, { name => 'shop', value => 'address' } )
|
||||
. WebGUI::Form::hidden( $session, { name => 'method', value => 'view' } )
|
||||
. WebGUI::Form::hidden( $session, { name => 'callback', value => $callbackJson } )
|
||||
. WebGUI::Form::submit( $session, { value => $i18n->get('choose billing address') } )
|
||||
. WebGUI::Form::formFooter( $session);
|
||||
|
||||
|
||||
# Generate 'Proceed' button
|
||||
my $proceedButton = $address
|
||||
? $self->ogoneCheckoutButton( $transaction, $address )
|
||||
: $i18n->get('please choose a billing address')
|
||||
;
|
||||
return $session->style->userStyle($addressButton.'<br />'.$billingAddressHtml.'<br />'.$proceedButton);
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 checkPostBackSHA ( )
|
||||
|
||||
Processes the postback data Ogone sends after a payment/cancelation. Figures out which transaction the data belongs
|
||||
to and checks whether the data isn't tampered with by comparing SHA hashes.
|
||||
|
||||
If everything checks out, returns the instanciated transaction object, otherwise returns undef.
|
||||
|
||||
=cut
|
||||
|
||||
sub checkPostbackSHA {
|
||||
my $self = shift;
|
||||
my $session = $self->session;
|
||||
my $form = $session->form;
|
||||
my $url = $session->url;
|
||||
|
||||
# Instanciate transaction
|
||||
my $transactionId = $url->unescape( $form->process( 'orderID' ) );
|
||||
my $transaction = WebGUI::Shop::Transaction->new( $session, $transactionId );
|
||||
return undef unless $transaction;
|
||||
|
||||
# Fetch and format amount from transaction
|
||||
my $amount = $transaction->get('amount');
|
||||
$amount =~ s/\.00$//; # remove trailing .00
|
||||
my $currency = $self->get('currency');
|
||||
|
||||
# Construct the passphrase...
|
||||
my $passphrase = join '',
|
||||
$transactionId, $currency, $amount,
|
||||
map( { $url->unescape( $form->process( $_ ) ) } qw{ PM ACCEPTANCE STATUS CARDNO PAYID NCERROR BRAND } ),
|
||||
$self->get('postbackSecret');
|
||||
|
||||
# and obtain its sha-1 hash in uppercase
|
||||
my $shaSignature = uc sha1_hex( $passphrase );
|
||||
|
||||
# Return the instanciated transaction if the hash is valid, else return undef.
|
||||
return $transaction if $shaSignature eq $form->process('SHASIGN');
|
||||
return undef;
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 _setPaymentStatus ( transactionSuccessful, ogoneId, statusCode, statusMessage )
|
||||
|
||||
Stores the results of a postback in the object for later use by other methods.
|
||||
|
||||
=head3 transactionSuccessful
|
||||
|
||||
A boolean indicating whether or not the payment was successful.
|
||||
|
||||
=head3 ogoneId
|
||||
|
||||
The Ogone issued transaction ID.
|
||||
|
||||
=head3 statusCode
|
||||
|
||||
The Ogone issued status code.
|
||||
|
||||
=head3 statusMessage
|
||||
|
||||
The ogone issued status message.
|
||||
|
||||
=cut
|
||||
|
||||
sub _setPaymentStatus {
|
||||
my $self = shift;
|
||||
my ($form, $url) = $self->session->quick( 'form', 'url' );
|
||||
|
||||
$self->{_transactionSuccessful} = shift || 0;
|
||||
$self->{_ogoneId} = shift || undef;
|
||||
$self->{_statusCode} = shift || undef;
|
||||
$self->{_statusMessage} = shift || undef;
|
||||
|
||||
$self->{_cartId} = $url->unescape( $form->process('COMPLUS') );
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 www_acceptTransaction ( )
|
||||
|
||||
The user is redirected to this screen when the payment was successful.
|
||||
|
||||
=cut
|
||||
|
||||
sub www_acceptTransaction {
|
||||
my $self = shift;
|
||||
my $session = $self->session;
|
||||
my $form = $session->form;
|
||||
|
||||
my $transaction = $self->checkPostbackSHA;
|
||||
return $session->style->userStyle('Invalid postback data.') unless $transaction;
|
||||
|
||||
if ( $form->process('NCERROR') == 0 ) {
|
||||
if ( !$transaction->isSuccessful ) {
|
||||
$self->_setPaymentStatus( 1, $form->process('PAYID'), $form->process('STATUS'), 'Complete' );
|
||||
$self->processTransaction( $transaction );
|
||||
}
|
||||
return $transaction->thankYou;
|
||||
}
|
||||
|
||||
return $session->style->userStyle( 'An error occurred with your transaction.' );
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 www_cancelTransaction ( )
|
||||
|
||||
The user is redirected to this screen when the payment was canceled.
|
||||
|
||||
=cut
|
||||
|
||||
sub www_cancelTransaction {
|
||||
my $self = shift;
|
||||
my $session = $self->session;
|
||||
my $form = $session->form;
|
||||
|
||||
my $transaction = $self->checkPostbackSHA;
|
||||
return $session->style->userStyle('Invalid postback data.') unless $transaction;
|
||||
|
||||
$self->_setPaymentStatus( 0, $form->process('PAYID'), $form->process('STATUS'), 'Cancelled' );
|
||||
$self->processTransaction( $transaction );
|
||||
|
||||
$session->http->setRedirect($self->session->url->getSiteURL.'?shop=cart');
|
||||
return $session->style->userStyle('Transaction cancelled');
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 www_declineTransaction ( )
|
||||
|
||||
The user is redirected to this screen when the payment was declined.
|
||||
|
||||
=cut
|
||||
|
||||
sub www_declineTransaction {
|
||||
my $self = shift;
|
||||
my $session = $self->session;
|
||||
my $form = $session->form;
|
||||
|
||||
my $transaction = $self->checkPostbackSHA;
|
||||
return $session->style->userStyle('Invalid postback data.') unless $transaction;
|
||||
|
||||
$self->_setPaymentStatus( 0, $form->process('PAYID'), $form->process('STATUS'), 'Declined' );
|
||||
$self->processTransaction( $transaction );
|
||||
|
||||
$session->http->setRedirect($self->session->url->getSiteURL.'?shop=cart');
|
||||
return $session->style->userStyle('Transaction declined');
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 www_exceptionTransaction ( )
|
||||
|
||||
The user is redirected to this screen when a payment exception occurred.
|
||||
|
||||
=cut
|
||||
|
||||
sub www_exceptionTransaction {
|
||||
my $self = shift;
|
||||
my $session = $self->session;
|
||||
my $form = $session->form;
|
||||
|
||||
my $transaction = $self->checkPostbackSHA;
|
||||
return $session->style->userStyle('Invalid postback data.') unless $transaction;
|
||||
|
||||
$self->_setPaymentStatus( 0, $form->process('PAYID'), $form->process('STATUS'), 'Transaction exception occurred' );
|
||||
$self->processTransaction( $transaction );
|
||||
|
||||
$session->http->setRedirect($self->session->url->getSiteURL.'?shop=cart');
|
||||
return $session->style->userStyle('A transaction exception occurred.');
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 www_edit ( )
|
||||
|
||||
Displays the properties screen.
|
||||
|
||||
|
||||
=cut
|
||||
|
||||
sub www_edit {
|
||||
my $self = shift;
|
||||
my $session = $self->session;
|
||||
my $admin = WebGUI::Shop::Admin->new($session);
|
||||
my $i18n = WebGUI::International->new($session, 'PayDriver_Ogone');
|
||||
|
||||
return $session->privilege->insufficient() unless $admin->canManage;
|
||||
|
||||
my $form = $self->getEditForm;
|
||||
$form->submit;
|
||||
|
||||
my $processUrl = $self->session->url->getSiteURL.'/?shop=pay;method=do;do=processTransaction;paymentGatewayId='.$self->getId;
|
||||
my $output = '<br />';
|
||||
$output .= sprintf $i18n->get('ogone setup'), $processUrl, $processUrl;
|
||||
|
||||
return $admin->getAdminConsole->render($form->print.$output, $i18n->get('payment methods','PayDriver'));
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 www_processTransaction ( )
|
||||
|
||||
This method is called by the post sale notfication.
|
||||
|
||||
=cut
|
||||
|
||||
sub www_processTransaction {
|
||||
my $self = shift;
|
||||
my $session = $self->session;
|
||||
my $form = $session->form;
|
||||
|
||||
my $transaction = $self->checkPostbackSHA;
|
||||
return $session->style->userStyle('Invalid postback data.') unless $transaction;
|
||||
|
||||
if ( $form->process('NCERROR') == 0 ) {
|
||||
if ( !$transaction->isSuccessful ) {
|
||||
$self->_setPaymentStatus( 1, $form->process('PAYID'), $form->process('STATUS'), 'Complete' );
|
||||
}
|
||||
}
|
||||
else {
|
||||
$self->_setPaymentStatus( 0, $form->process('PAYID'), $form->process('STATUS'), 'A payment processing error occurred' );
|
||||
}
|
||||
|
||||
$self->processTransaction( $transaction );
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue