diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index f05d7abbf..12116dd1f 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -1,5 +1,6 @@ 7.7.7 - rfe #10061: Use email as username at registration + - Added Ogone payment plugin ( Martin Kamerbeek / Oqapi ) 7.7.6 - Added mobile style template. If enabled in settings, will serve alternate style templates diff --git a/docs/gotcha.txt b/docs/gotcha.txt index 4799c1695..86453d075 100644 --- a/docs/gotcha.txt +++ b/docs/gotcha.txt @@ -8,6 +8,10 @@ versions. Be sure to heed the warnings contained herein as they will save you many hours of grief. +7.7.7 +-------------------------------------------------------------------- + * WebGUI now requires Digest::SHA. + 7.7.6 -------------------------------------------------------------------- * WebGUI now requires Business::Tax::VAT::Validation. diff --git a/docs/upgrades/upgrade_7.7.6-7.7.7.pl b/docs/upgrades/upgrade_7.7.6-7.7.7.pl index 6196d9b4a..6fb7bd259 100644 --- a/docs/upgrades/upgrade_7.7.6-7.7.7.pl +++ b/docs/upgrades/upgrade_7.7.6-7.7.7.pl @@ -27,23 +27,23 @@ use WebGUI::Asset; my $toVersion = '7.7.7'; my $quiet; # this line required - my $session = start(); # this line required -# upgrade functions go here +addOgoneToConfig( $session ); addUseEmailAsUsernameToSettings( $session ); finish($session); # this line required #---------------------------------------------------------------------------- -# Describe what our function does -#sub exampleFunction { -# my $session = shift; -# print "\tWe're doing some stuff here that you should know about... " unless $quiet; -# # and here's our code -# print "DONE!\n" unless $quiet; -#} +sub addOgoneToConfig { + my $session = shift; + print "\tAdding Ogone payment plugin..." unless $quiet; + + $session->config->addToArray('paymentDrivers', 'WebGUI::Shop::PayDriver::Ogone'); + + print "Done\n" unless $quiet; +} #---------------------------------------------------------------------------- sub addUseEmailAsUsernameToSettings { @@ -51,10 +51,10 @@ sub addUseEmailAsUsernameToSettings { print "\tAdding webguiUseEmailAsUsername to settings \n" unless $quiet; $session->db->write("insert into settings (name, value) values ('webguiUseEmailAsUsername',0)"); + print "Done.\n" unless $quiet; } - # -------------- DO NOT EDIT BELOW THIS LINE -------------------------------- #---------------------------------------------------------------------------- diff --git a/lib/WebGUI/Shop/PayDriver/Ogone.pm b/lib/WebGUI/Shop/PayDriver/Ogone.pm new file mode 100644 index 000000000..809a15e81 --- /dev/null +++ b/lib/WebGUI/Shop/PayDriver/Ogone.pm @@ -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.'
'.$billingAddressHtml.'
'.$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 = '
'; + $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; + diff --git a/lib/WebGUI/i18n/English/PayDriver_Ogone.pm b/lib/WebGUI/i18n/English/PayDriver_Ogone.pm new file mode 100755 index 000000000..936085908 --- /dev/null +++ b/lib/WebGUI/i18n/English/PayDriver_Ogone.pm @@ -0,0 +1,135 @@ +package WebGUI::i18n::English::PayDriver_Ogone; +use strict; + +our $I18N = { + 'Ogone' => { + message => q|Ogone|, + lastUpdated => 0, + context => q|The name of the Ogone plugin|, + }, + + 'psp id' => { + message => q|PSP ID|, + lastUpdated => 0, + context => q|Label of a setting in the ogone config screen.|, + }, + + 'psp id help' => { + message => q|Your ogone username|, + lastUpdated => 0, + context => q|Hover help of a setting in the ogone config screen.|, + }, + + 'sha secret' => { + message => q|Pre payment SHA secret (option 3.2)|, + lastUpdated => 0, + context => q|Label of a setting in the ogone config screen.|, + }, + + 'sha secret help' => { + message => q|The passphrase you set in section 3.2 in the Technical information page of the Ogone interface.|, + lastUpdated => 0, + context => q|Hover help of a setting in the ogone config screen.|, + }, + + 'postback secret' => { + message => q|Post payment SHA secret (option 4.4)|, + lastUpdated => 0, + context => q|Label of a setting in the ogone config screen.|, + }, + + 'postback secret help' => { + message => q|The passphrase you set in section 4.4 in the Technical information page of the Ogone interface.|, + lastUpdated => 0, + context => q|Hover help of a setting in the ogone config screen.|, + }, + + 'locale' => { + message => q|Ogone language|, + lastUpdated => 0, + context => q|Label of a setting in the ogone config screen.|, + }, + + 'locale help' => { + message => q|The locale string for the language the Ogone interface should be displayed in to the user (eg. nl_NL or en_US) |, + lastUpdated => 0, + context => q|Hover help of a setting in the ogone config screen.|, + }, + + 'currency' => { + message => q|Currency (ISO Aplha code)|, + lastUpdated => 0, + context => q|Label of a setting in the ogone config screen.|, + }, + + 'currency help' => { + message => q|The currency in which the payment are to be made. Enter the ISO Alpha code. Commonly used codes are EUR for Euro, USD for US Dollar, CHF for Swiss Franks and GBP for Brittish Pounds. See http://en.wikipedia.org/wiki/ISO_currency_code#Active_codes for a complete list.|, + lastUpdated => 0, + context => q|Hover help of a setting in the ogone config screen.|, + }, + + 'use test mode' => { + message => q|Use in test mode?|, + lastUpdated => 0, + context => q|Label of a setting in the ogone config screen.|, + }, + + 'use test mode help' => { + message => q|Setting this option to yes directs all payment requests to Ogone's test environment. This allows you to check if everything is set up correctlt before going live. No actual payments are being made while test mode is enabled, so don't forget to set this option to 'No' when ready testing.|, + lastUpdated => 0, + context => q|Hover help of a setting in the ogone config screen.|, + }, + + 'pay' => { + message => q|Pay|, + lastUpdated => 0, + context => q|Label of the pay button.|, + }, + + 'choose billing address' => { + message => q|Choose billing address|, + lastUpdated => 0, + context => q|Label of the choose address button.|, + }, + + 'please choose a billing address' => { + message => q|Please choose a billing address.|, + lastUpdated => 0, + context => q|Status message|, + }, + + 'ogone setup' => { + message => q| +

In order to use this plugin you need to set up Ogone as well. Please go to the Techincal Information + page in the Ogone admin interface and set the properties listed below. Always start in test mode and + check if everything work alright. When switching to production mode, don't forget to apply the option + below to your production account as well.

+ |, + lastUpdated => 0, + context => q|Text that describes the required Ogone settings.|, + }, +}; + +1; + diff --git a/sbin/testEnvironment.pl b/sbin/testEnvironment.pl index 1404f7ac4..a8143691b 100755 --- a/sbin/testEnvironment.pl +++ b/sbin/testEnvironment.pl @@ -129,6 +129,7 @@ checkModule('CSS::Packer', '0.2' ); checkModule('Business::Tax::VAT::Validation', '0.20' ); checkModule('Crypt::SSLeay', '0.57' ); checkModule('Scope::Guard', '0.03' ); +checkModule('Digest::SHA', '5.47' ); failAndExit("Required modules are missing, running no more checks.") if $missingModule; diff --git a/t/Shop/PayDriver/Ogone.t b/t/Shop/PayDriver/Ogone.t new file mode 100644 index 000000000..d4bbb584f --- /dev/null +++ b/t/Shop/PayDriver/Ogone.t @@ -0,0 +1,591 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2008 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 +#------------------------------------------------------------------ + +# Write a little about what this script tests. +# +# + +use FindBin; +use strict; +use lib "$FindBin::Bin/../../lib"; +use Test::More; +use Test::Deep; +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; +use JSON; +use HTML::Form; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; + + +#---------------------------------------------------------------------------- +# Tests + +my $tests = 45; +plan tests => 1 + $tests; + +#---------------------------------------------------------------------------- +# figure out if the test can actually run + +my $e; + +diag('Testing existence'); +my $loaded = use_ok('WebGUI::Shop::PayDriver::Ogone'); + +SKIP: { + +skip 'Unable to load module WebGUI::Shop::PayDriver::Ogone', $tests unless $loaded; + +####################################################################### +# +# definition +# +####################################################################### + +diag('Testing definition'); +my $definition; + +eval { $definition = WebGUI::Shop::PayDriver::Ogone->definition(); }; +$e = Exception::Class->caught(); +isa_ok ($e, 'WebGUI::Error::InvalidParam', 'definition takes an exception to not giving it a session variable'); +cmp_deeply ( + $e, + methods( + error => 'Must provide a session variable', + ), + 'definition: requires a session variable', +); + +$definition = WebGUI::Shop::PayDriver::Ogone->definition($session); + +use Data::Dumper; +my $expectDefinition = { + name => 'Ogone', + properties => { + pspid => { + fieldType => 'text', + label => ignore(), + hoverHelp => ignore(), + defaultValue => q{} + }, + shaSecret => { + fieldType => 'password', + label => ignore(), + hoverHelp => ignore(), + }, + postbackSecret => { + fieldType => 'password', + label => ignore(), + hoverHelp => ignore(), + }, + locale => { + fieldType => 'text', + label => ignore(), + hoverHelp => ignore(), + defaultValue => 'en_US', + maxlength => 5, + size => 5, + }, + currency => { + fieldType => 'text', + label => ignore(), + hoverHelp => ignore(), + defaultValue => 'EUR', + maxlength => 3, + size => 3, + }, + useTestMode => { + fieldType => 'yesNo', + label => ignore(), + hoverHelp => ignore(), + defaultValue => 1, + }, + }, +}; + +cmp_deeply ( $definition->[0], $expectDefinition, 'Definition returns an array of hashrefs' ); + +$definition = WebGUI::Shop::PayDriver::Ogone->definition($session, [ { name => 'Ogone First' }]); + +cmp_deeply ( + $definition, + [ + { + name => 'Ogone First', + }, + { + name => 'Ogone', + properties => ignore(), + }, + { + name => 'Payment Driver', + properties => ignore(), + } + ], + , + 'New data is appended correctly', +); + +####################################################################### +# +# create +# +####################################################################### + +my $driver; + +# Test incorrect for parameters + +eval { $driver = WebGUI::Shop::PayDriver::Ogone->create(); }; +$e = Exception::Class->caught(); +isa_ok ($e, 'WebGUI::Error::InvalidParam', 'create takes exception to not giving it a session object'); +cmp_deeply ( + $e, + methods( + error => 'Must provide a session variable', + ), + 'create takes exception to not giving it a session object', +); + +eval { $driver = WebGUI::Shop::PayDriver::Ogone->create($session, {}); }; +$e = Exception::Class->caught(); +isa_ok ($e, 'WebGUI::Error::InvalidParam', 'create takes exception to giving it an empty hashref of options'); +cmp_deeply ( + $e, + methods( + error => 'Must provide a hashref of options', + ), + 'create takes exception to not giving it an empty hashref of options', +); + +# Test functionality +my $signature = '-----BEGIN PKCS7----- +MIIHPwYJKoZIhvcNAQcEoIIHMDCCBywCAQExggE0MIIB +MAIBADCBmDCBkjELMAkGA1UEBhMCVVMxCzAJBgNVBAgT +AkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYD +VQQKEwtQYXlQYWwgSW5jLjEVMBMGA1UECxQMc3RhZ2Ux +X2NlcnRzMRMwEQYDVQQDFApzdGFnZTFfYXBpMRwwGgYJ +KoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tAgEAMA0GCSqG +SIb3DQEBAQUABIGAiJLqJ8905lNbvKoa715KsOJtSOGy +4d6fEKV7+S8KU8E/RK0SFmMgGPRpmXdzx9MXCU43/tXj +lyuyOeZQUBaAIaWoNpfZmBUYIvJVh4W+bDH6JUkugelp +CaTjxXOx/F1qj79D9z06AK+N3yW1fM41fM7X9Q1Bc12g +THjJUKXcIIcxCzAJBgUrDgMCGgUAMIGkBgkqhkiG9w0B +BwEwFAYIKoZIhvcNAwcECOsHG9QOvcJFgIGAwmbN5Acd +cnCH0ZTnsSOq5GtXeQf0j2jCBCg6y7b4ZXQwgdqUC/7x +eb0yicuiRVuRB9WLr/0rGFuSYENpKVUqWYjnlg3TsxLP +IxDCp6lfFqsrclppyZ9CP+xim7y0qKqZZufJG8HgCHxk +3BPD6LqByjQjDVpqKKmCNJ1HlwXGN+SgggOWMIIDkjCC +AvugAwIBAgIBADANBgkqhkiG9w0BAQQFADCBkzELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAkNBMREwDwYDVQQHEwhT +YW4gSm9zZTEPMA0GA1UEChMGUGF5UGFsMRwwGgYDVQQL +ExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRMwEQYDVQQDEwpT +b3V2aWsgRGFzMSAwHgYJKoZIhvcNAQkBFhFzb3VkYXNA +cGF5cGFsLmNvbTAeFw0wNDA1MjExODE4NTBaFw0wNDA2 +MjAxODE4NTBaMIGTMQswCQYDVQQGEwJVUzELMAkGA1UE +CBMCQ0ExETAPBgNVBAcTCFNhbiBKb3NlMQ8wDQYDVQQK +EwZQYXlQYWwxHDAaBgNVBAsTE1N5c3RlbXMgRW5naW5l +ZXJpbmcxEzARBgNVBAMTClNvdXZpayBEYXMxIDAeBgkq +hkiG9w0BCQEWEXNvdWRhc0BwYXlwYWwuY29tMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDatyhVzmVe+kCN +tOSNS+c7p9pNHlFGbGtIWgIAKSOVlaTk4JD/UAvQzYnn +eWPUk+Xb5ShTx8YRDEtRtecy/PwSIIrtS2sC8RrmjZxU +uNRqPB6y1ahGwGcNd/wOIy3FekGE/ctX7oG6/Voz/E2Z +EyJaPm7KwYiDQYz7kWJ6eB+kDwIDAQABo4HzMIHwMB0G +A1UdDgQWBBQx23WZRMmnADSXDr+P7uxORBdDuzCBwAYD +VR0jBIG4MIG1gBQx23WZRMmnADSXDr+P7uxORBdDu6GB +maSBljCBkzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNB +MREwDwYDVQQHEwhTYW4gSm9zZTEPMA0GA1UEChMGUGF5 +UGFsMRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5n +MRMwEQYDVQQDEwpTb3V2aWsgRGFzMSAwHgYJKoZIhvcN +AQkBFhFzb3VkYXNAcGF5cGFsLmNvbYIBADAMBgNVHRME +BTADAQH/MA0GCSqGSIb3DQEBBAUAA4GBAIBlMsXVnxYe +ZtVTG3rsVYePdkMs+0WdRd+prTK4ZBcAkCyNk9jCq5dy +VziCi4ZCleMqR5Y0NH1+BQAf8vxxcb4Z7p0rryXGb96f +ZfkSYd99a4qGKW3aSIsc2kpaC/ezQg8vuD6JSo6VhJIb +Zn0oWajvkHNMENOwN/Ym5stvAxtnMYIBnzCCAZsCAQEw +gZkwgZMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTER +MA8GA1UEBxMIU2FuIEpvc2UxDzANBgNVBAoTBlBheVBh +bDEcMBoGA1UECxMTU3lzdGVtcyBFbmdpbmVlcmluZzET +MBEGA1UEAxMKU291dmlrIERhczEgMB4GCSqGSIb3DQEJ +ARYRc291ZGFzQHBheXBhbC5jb20CAQAwCQYFKw4DAhoF +AKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJ +KoZIhvcNAQkFMQ8XDTA0MDUyNjE5MTgxNFowIwYJKoZI +hvcNAQkEMRYEFI2w1oe5qvHYB0w9Z/ntkRcDqLlhMA0G +CSqGSIb3DQEBAQUABIGAimA3r6ZXmyynFGF5cOj6E1Hq +Ebtelq2tg4HroAHZLWoQ3kc/7IM0LCuWZmgtD5739NSS +0+tOFSdH68sxKsdooR3MFTbdzWhtej5fPKRa6BfHGPjI +9R9NoAQBmaeUuOiPSeVTzXDOKDbZB0sJtmWNeueTD9D0 +BOu+vkC1g+HRToc= +-----END PKCS7-----'; + +my $options = { + label => 'Fast and harmless', + enabled => 1, + group => 3, + receiptMessage => 'Pannenkoeken zijn nog lekkerder met kaas', + vendorId => 'oqapi', + signature => $signature, + currency => 'EUR', + useSandbox => '0', + emailMessage => 'Thank you very very much' +}; + +$driver = WebGUI::Shop::PayDriver::Ogone->create( $session, $options ); + +isa_ok ($driver, 'WebGUI::Shop::PayDriver::Ogone', 'create creates WebGUI::Shop::PayDriver object'); +like($driver->getId, $session->id->getValidator, 'driver id is a valid GUID'); + +my $dbData = $session->db->quickHashRef('select * from paymentGateway where paymentGatewayId=?', [ $driver->getId ]); + +cmp_deeply ( + $dbData, + { + paymentGatewayId => $driver->getId, + className => ref $driver, + options => ignore() + }, + 'Correct data written to the db', +); +my $paymentGatewayOptions = from_json($dbData->{'options'}); +cmp_deeply ( + $paymentGatewayOptions, + { + "group" => 3, + "receiptMessage" => 'Pannenkoeken zijn nog lekkerder met kaas', + "label" => 'Fast and harmless', + "enabled" => 1, + "vendorId" => 'oqapi', + "signature" => $signature, + "currency" => 'EUR', + "useSandbox" => '0', + "emailMessage" => 'Thank you very very much' + }, + 'Correct options are written to the db' + +); + +####################################################################### +# +# session +# +####################################################################### + +isa_ok ($driver->session, 'WebGUI::Session', 'session method returns a session object'); +is ($session->getId, $driver->session->getId, 'session method returns OUR session object'); + +####################################################################### +# +# paymentGatewayId, getId +# +####################################################################### + +like ($driver->paymentGatewayId, $session->id->getValidator, 'got a valid GUID for paymentGatewayId'); +is ($driver->getId, $driver->paymentGatewayId, 'getId returns the same thing as paymentGatewayId'); + +####################################################################### +# +# className +# +####################################################################### + +is ($driver->className, ref $driver, 'className property set correctly'); + +####################################################################### +# +# options +# +####################################################################### + +cmp_deeply ($driver->options, $options, 'options accessor works'); + +####################################################################### +# +# getName +# +####################################################################### + +eval { WebGUI::Shop::PayDriver->getName(); }; +$e = Exception::Class->caught(); +isa_ok ($e, 'WebGUI::Error::InvalidParam', 'getName requires a session object passed to it'); +cmp_deeply ( + $e, + methods( + error => 'Must provide a session variable', + ), + 'getName requires a session object passed to it', +); + +is (WebGUI::Shop::PayDriver->getName($session), 'Payment Driver', 'getName returns the human readable name of this driver'); + +####################################################################### +# +# get +# +####################################################################### + +cmp_deeply ($driver->get, $driver->options, 'get works like the options method with no param passed'); +is ($driver->get('enabled'), 1, 'get the enabled entry from the options'); +is ($driver->get('label'), 'Fast and harmless', 'get the label entry from the options'); + +my $optionsCopy = $driver->get; +$optionsCopy->{label} = 'And now for something completely different'; +isnt( + $driver->get('label'), + 'And now for something completely different', + 'hashref returned by get() is a copy of the internal hashref' +); + +####################################################################### +# +# getCart +# +####################################################################### + +my $cart = $driver->getCart; +isa_ok ($cart, 'WebGUI::Shop::Cart', 'getCart returns an instantiated WebGUI::Shop::Cart object'); + +####################################################################### +# +# getEditForm +# +####################################################################### + +my $form = $driver->getEditForm; + +isa_ok ($form, 'WebGUI::HTMLForm', 'getEditForm returns an HTMLForm object'); + +my $html = $form->print; + +##Any URL is fine, really +my @forms = HTML::Form->parse($html, 'http://www.webgui.org'); +is (scalar @forms, 1, 'getEditForm generates just 1 form'); + +my @inputs = $forms[0]->inputs; +is (scalar @inputs, 19, 'getEditForm: the form has 19 controls'); + +my @interestingFeatures; +foreach my $input (@inputs) { + my $name = $input->name; + my $type = $input->type; + push @interestingFeatures, { name => $name, type => $type }; +} + +cmp_deeply( + \@interestingFeatures, + [ + { + name => undef, + type => 'submit', + }, + { + name => 'shop', + type => 'hidden', + }, + { + name => 'method', + type => 'hidden', + }, + { + name => 'do', + type => 'hidden', + }, + { + name => 'paymentGatewayId', + type => 'hidden', + }, + { + name => 'className', + type => 'hidden', + }, + { + name => 'label', + type => 'text', + }, + { + name => 'enabled', + type => 'radio', + }, + { + name => 'groupToUse', + type => 'option', + }, + { + name => '__groupToUse_isIn', + type => 'hidden', + }, + { + name => 'receiptEmailTemplateId', + type => 'option', + }, + { + name => 'saleNotificationGroupId', + type => 'option', + }, + { + name => '__saleNotificationGroupId_isIn', + type => 'hidden', + }, + { + name => 'pspid', + type => 'text', + }, + { + name => 'shaSecret', + type => 'password', + }, + { + name => 'postbackSecret', + type => 'password', + }, + { + name => 'locale', + type => 'text', + }, + { + name => 'currency', + type => 'text', + }, + { + name => 'useTestMode', + type => 'radio', + }, + ], + 'getEditForm made the correct form with all the elements' + +); + +####################################################################### +# +# new +# +####################################################################### + +my $oldDriver; + +eval { $oldDriver = WebGUI::Shop::PayDriver::Ogone->new(); }; +$e = Exception::Class->caught(); +isa_ok ($e, 'WebGUI::Error::InvalidParam', 'new takes exception to not giving it a session object'); +cmp_deeply ( + $e, + methods( + error => 'Must provide a session variable', + ), + 'new takes exception to not giving it a session object', +); + +eval { $oldDriver = WebGUI::Shop::PayDriver::Ogone->new($session); }; +$e = Exception::Class->caught(); +isa_ok ($e, 'WebGUI::Error::InvalidParam', 'new takes exception to not giving it a paymentGatewayId'); +cmp_deeply ( + $e, + methods( + error => 'Must provide a paymentGatewayId', + ), + 'new takes exception to not giving it a paymentGatewayId', +); + +eval { $oldDriver = WebGUI::Shop::PayDriver::Ogone->new($session, 'notEverAnId'); }; +$e = Exception::Class->caught(); +isa_ok ($e, 'WebGUI::Error::ObjectNotFound', 'new croaks unless the requested paymentGatewayId object exists in the db'); +cmp_deeply ( + $e, + methods( + error => 'paymentGatewayId not found in db', + id => 'notEverAnId', + ), + 'new croaks unless the requested paymentGatewayId object exists in the db', +); + +my $driverCopy = WebGUI::Shop::PayDriver::Ogone->new($session, $driver->getId); + +is ($driver->getId, $driverCopy->getId, 'same id'); +is ($driver->className, $driverCopy->className, 'same className'); +cmp_deeply ($driver->options, $driverCopy->options, 'same options'); + +####################################################################### +# +# update +# +####################################################################### + +eval { $driver->update(); }; +$e = Exception::Class->caught(); +isa_ok ($e, 'WebGUI::Error::InvalidParam', 'update takes exception to not giving it a hashref of options'); +cmp_deeply ( + $e, + methods( + error => 'update was not sent a hashref of options to store in the database', + ), + 'update takes exception to not giving it a hashref of options', +); + +my $newOptions = { + label => 'Yet another label', + enabled => 0, + group => 4, + receiptMessage => 'Dropjes!', +}; + +$driver->update($newOptions); +my $storedOptions = $session->db->quickScalar('select options from paymentGateway where paymentGatewayId=?', [ + $driver->getId, +]); +cmp_deeply( + $newOptions, + from_json($storedOptions), + , + 'update() actually stores data', +); + + +####################################################################### +# +# canUse +# +####################################################################### + +$session->user({userId => 3}); +ok($driver->canUse, 'canUse: session->user is used if no argument is passed'); +ok(!$driver->canUse({userId => 1}), 'canUse: userId explicit works, visitor cannot use this driver'); + + +####################################################################### +# +# delete +# +####################################################################### + +$driver->delete; + +my $count = $session->db->quickScalar('select count(*) from paymentGateway where paymentGatewayId=?', [ + $driver->paymentGatewayId +]); + +is ($count, 0, 'delete deleted the object'); + +undef $driver; + + + + +#---------------------------------------------------------------------------- +# Cleanup + + + +} +END { + +} +#vim:ft=perl