From 61d74619c5c3838fef357f8f064a142a701f1603 Mon Sep 17 00:00:00 2001 From: Martin Kamerbeek Date: Mon, 10 Jan 2005 16:06:11 +0000 Subject: [PATCH] Adding POD to the commerce system, as well as objectifying everything. --- lib/WebGUI/Commerce/Item.pm | 154 +++++++++++ lib/WebGUI/Commerce/Payment.pm | 400 +++++++++++++++++++++++++++- lib/WebGUI/Commerce/ShoppingCart.pm | 102 +++++++ lib/WebGUI/Commerce/Transaction.pm | 172 ++++++++++++ 4 files changed, 827 insertions(+), 1 deletion(-) diff --git a/lib/WebGUI/Commerce/Item.pm b/lib/WebGUI/Commerce/Item.pm index febdfe0db..140edcabc 100644 --- a/lib/WebGUI/Commerce/Item.pm +++ b/lib/WebGUI/Commerce/Item.pm @@ -1,6 +1,135 @@ package WebGUI::Commerce::Item; +=head1 NAME + +Package WebGUI::Commerce::Item + +=head1 DESCRIPTION + +This is the SUPER class off all Item plugins. Item plugins are an abstraction layer to connect +arbitrary types of products and other stuff you might want to sell to the commerce system. + +The SUPER class new method provides an easy way to load Item plugins: WebGUI::Item->new('1234', 'MyItem') +is equivalent to WebGUI::Item::MyItem->new('1234'). The SUPER class new has the benefit of added +error checking, so you should use this. + + +=head1 SYNOPSIS + + use WebGUI::Item; + $item = WebGUI::Item->new($itemId, $itemType); + + $description = $item->description; + $duration = $item->duration; + $item->handler; + $id = $item->id + $isRecurring = $item->isRecurring; + $name = $item->name; + $price = $item->price; + $type = $item->type; + +=head1 METHODS + +These methods are available from this class: + +=cut + #------------------------------------------------------------------- + +=head2 description ( ) + +This returns the description of the item. This must be implemented by an item plugin. + +=cut + +sub description { + return WebGUI::ErrorHandler::fatalError('The description method of WebGUI::Commerce::Item must be overridden.'); +} + +#------------------------------------------------------------------- + +=head2 duration ( ) + +This returns the duration of a term when the item is recurring. If your item isn't +recurring you don't need to override this method. If you do however, you should return +undef if it's not recurring. + +=cut + +sub duration { + return undef; +} + +#------------------------------------------------------------------- + +=head2 handler ( ) + +This will execute the handler that's tied to this item. If you don't have a handler for +your item you don't have to override this method or if you do, you can just return undef. + +=cut + +sub handler { + return undef; +} + +#------------------------------------------------------------------- + +=head2 id ( ) + +This returns the item ID. This must be implemented by an item plugin. This must be implemented +by an item plugin. + +=cut + +sub id { + return WebGUI::ErrorHandler::fatalError('The id method of WebGUI::Commerce::Item must be overridden.'); +} + +#------------------------------------------------------------------- + +=head2 isRecurring ( ) + +A boolean identifying wheter the item is recurring (like, for instance, a subscription) or not. +You must override this method if your item is recurring. + +=cut + +sub isRecurring { + return 0; +} + +#------------------------------------------------------------------- + +=head2 name ( ) + +Returns the name of the item. This must be implemented by an item plugin. + +=cut + +sub name { + return WebGUI::ErrorHandler::fatalError('The name method of WebGUI::Commerce::Item must be overridden.'); +} + +#------------------------------------------------------------------- + +=head2 new ( itemdId, itemType ) + +Returns an item object of type itemType and with id itemId. Note that this is an easy way to load +item plugins. Your custom plugin should also have a new method that returns the actual item object. + +The new method of the plugin won't overload this method, since there's no inheritance. + +=head3 itemId + +The id of the item you want to load. + +=head3 itemType + +The type (namespace) of the item you want. + +=cut + sub new { my ($class, $namespace, $load, $cmd, $plugin); $class = shift; @@ -16,5 +145,30 @@ sub new { return $plugin; } +#------------------------------------------------------------------- + +=head2 price ( ) + +This method should return the price of the item. If the item is recurring this should be the per +term price. This must be implemented by an item plugin. + +=cut + +sub price { + return WebGUI::ErrorHandler::fatalError('The price method of WebGUI::Commerce::Item must be overridden.'); +} + +#------------------------------------------------------------------- + +=head2 type ( ) + +Returns the type (namespace) of the item. + +=cut + +sub type { + return WebGUI::ErrorHandler::fatalError('The type method of WebGUI::Commerce::Item must be overridden.'); +} + 1; diff --git a/lib/WebGUI/Commerce/Payment.pm b/lib/WebGUI/Commerce/Payment.pm index f3b186efc..b6acd919b 100644 --- a/lib/WebGUI/Commerce/Payment.pm +++ b/lib/WebGUI/Commerce/Payment.pm @@ -6,7 +6,76 @@ use WebGUI::International; use Tie::IxHash; use WebGUI::HTMLForm; +=head1 NAME + +Package WebGUI::Commerce::Payment + +=head1 DESCRIPTION + +An abstract class for all payment plugins to extend. + +=head1 SYNOPSIS + + use WebGUI::CommercePayment; + our @ISA = qw(WebGUI::Commerce::Payment); + +Invoking goes as follows: + + $plugin = WebGUI::Commerce::Payment->new('MyPlugin'); + +=head1 METHODS + +These methods are available from this class: + +=cut + + #------------------------------------------------------------------- + +=head2 cancelRecurringPayment ( data ) + +This method takes care of canceling a recurring transaction. You must override this +method if your plugin can handle recurring payments. + +=head3 data + +A hashref containing: + + id => the gateway ID of the transaction, + transaction => the instanciated WebGUI::Commerce::Transaction object + +=cut + +sub cancelRecurringPayment { + return ""; +} + +#------------------------------------------------------------------- + +=head2 checkoutForm + +This must return a printRowsOnly'ed WebGUI::HTMLForm containing the fields for the checkout +dat you want to collect. Do not include submit buttons. You probably want to override this +method. + +=cut + +sub checkoutForm { + return ""; +} + +#------------------------------------------------------------------- + +=head2 configurationForm + +This generates the configuration form that's displayed in the admin console. You must +extend this method to include parameters specific to this payment module. To do so return +the SUPER::configurationForm method with a printRowsOnly'ed WebGUI::HTMLForm as the argument. + +Also be sure to prepend all formfield names with the prepend method. See propend for more info. + +=cut + sub configurationForm { my ($self, $form, $f); $self = shift; @@ -23,18 +92,86 @@ sub configurationForm { return $f->printRowsOnly; } - + #------------------------------------------------------------------- + +=head2 confirmRecurringTransaction + +This method is called if your gateway signals you (ie. posts data to some URL) to confirm a +recurring payment term has been processed. If this is the case, you probably want to store +the result in some table so it can be processed by the Schedualer plugin through the +getRecurringPaymentStatus method. + +You only need to override this method if your gateway uses a webbased contacting scheme. + +=cut + +sub confirmRecurringTransaction { + return undef; +} + +#------------------------------------------------------------------- + +=head2 confirmTransaction + +This method is called when your gateway contacts a specific URL to notify you of the result of a +transaction. You should override this method only if your gateway uses this kind of notification +(ie. like PayPal APN). Returns a boolean indicating whether the transaction was successful or not. + +=cut + +sub confirmTransaction { + return 0; +} + +#------------------------------------------------------------------- + +=head2 connectionError + +Returns an error message if there was a connection error. You must override this method. + +=cut + +sub connectionError { + return "The connetionError method must be overridden."; +} + +#------------------------------------------------------------------- + +=head2 enabled + +Returns a boolean indicating whether the plugin is enabled or not. + +=cut + sub enabled { return $_[0]->{_enabled}; } #------------------------------------------------------------------- + +=head2 get ( property ) + +Returns property of the plugin. + +=head3 property + +The name of the property you want. + +=cut + sub get { return $_[0]->{_properties}{$_[1]}; } #------------------------------------------------------------------- + +=head2 getEnabledPlugins + +Returns a reference to an array of all enabled instanciated payment plugins. + +=cut + sub getEnabledPlugins { my (@enabledPlugins, $plugin, @plugins); @enabledPlugins = WebGUI::SQL->buildArray("select namespace from commerceSettings where type='Payment' and fieldName='enabled' and fieldValue='1'"); @@ -48,6 +185,17 @@ sub getEnabledPlugins { } #------------------------------------------------------------------- + +=head2 init ( namespace ) + +Constructor for the plugin. You should extend this method. + +=head3 namespace + +The namespace of the plugin. + +=cut + sub init { my ($class, $namespace, $properties); $class = shift; @@ -59,6 +207,68 @@ sub init { } #------------------------------------------------------------------- + +=head2 gatewayId + +Returns the gatewayId of the transaction. You must override this method. + +=cut + +sub gatewayId { + return WebGUI::ErrorHandler::fatalError("You must override the gatewayId method in your Payment plugin."); +} + +#------------------------------------------------------------------- + +=head2 getRecurringPaymentStatus ( recurringId, term ) + +This should return a hashref containing the payment status of the specified term. If +the term has not been processed yet this method should return undef. Override only if +your plugin is capable of recurring transactions. + +The hashref should contain: + + resultCode => the result of the payment + +=head3 recurringId + +The ID the gateway has assigned to the recurring transaction. + +=head3 term + +The term number you want the status of. + +=cut + +sub getRecurringPaymentStatus { + return undef; +} + +#------------------------------------------------------------------- + +=head2 errorCode + +Returns the error code of the last submission. + +=cut + +sub errorCode { + return WebGUI::ErrorHandler::fatalError("You must override thie errorCode method in the payment plugin."); +} + +#------------------------------------------------------------------- + +=head2 load ( namespace ) + +A convienient method to load a plugin. It handles all error checking and stuff for you. +This is a SUPER class method only and shoud NOT be overridden. + +=head3 namespace + +The namespace of the plugin. + +=cut + sub load { my ($class, $namespace, $load, $cmd, $plugin); $class = shift; @@ -74,11 +284,121 @@ sub load { } #------------------------------------------------------------------- + +=head2 name + +Returns the (display) name of the plugin. You must override this method. + +=cut + +sub name { + return WebGUI::ErrorHandler::fatalError("You must override the name method in the payment plugin."); +} + +#------------------------------------------------------------------- + +=head2 namespace + +Returns the namespace of the plugin. + +=cut + sub namespace { return $_[0]->{_namespace}; } #------------------------------------------------------------------- + +=head2 normalTransaction ( transactionData ) + +This method submits a normal (non-recurring) transaction to the payment gateway. You probably +should override this method. + +=head3 transactionData + +A hashref containing: + + amount => the total amount of the transaction + description => the transaction description + invoiceNumber => the invoice number of the transaction + id => the webgui transaction ID + +=cut + +sub normalTransaction { + return undef; +} + +#------------------------------------------------------------------- + +=head2 recurringTransaction ( transactionData ) + +This method submits a recurring transaction to the payment gateway. You must override +this method if your plugin supports recurring payments. + +=head3 transactionData + +A hashref containing: + + amount => the total amount of the transaction, + term => the number of terms of the subscription should last. + If none is given your plugin should use an infinite number of terms, + payPeriod => the billing interval, + description => the transaction description, + invoiceNumber => the invoice number of the transaction, + id => the webgui transaction ID, + +=cut + +sub recurringTransaction { + return undef; +} + +#------------------------------------------------------------------- + +=head2 resultCode + +Returns the result code of the transaction. You must override this method. + +=cut + +sub resultCode { + return WebGUI::ErrorHandler::fatalError("You must override the resultCode method in the payment plugin."); +} + +#------------------------------------------------------------------- + +=head2 resultMessage + +Returns the result message of the transaction. You must override this method. + +=cut + +sub resultMessage { + return WebGUI::ErrorHandler::fatalError("You must override the resultMessage method in the payment plugin."); +} + +#------------------------------------------------------------------- + +=head2 prepend ( fieldName ) + +A utility method that prepends fieldName with a string that's used to save configuration data to +the database. Use it on all fields in the configurationForm method. + +For instance: + + $f = WebGUI::HTMLForm->new; + $f->text( + -name => $self->prepend('MyField'); + -label => 'MyField' + ); + +=head3 fieldName + +The string to prepend. + +=cut + sub prepend { my ($self, $name); $self = shift; @@ -88,6 +408,17 @@ sub prepend { } #------------------------------------------------------------------- + +=head2 recurringPeriodValues ( period ) + +A utility method that returns the internationalized name for period. + +=head3 period + +The period you want the name for. + +=cut + sub recurringPeriodValues { my ($i18n, %periods); $i18n = WebGUI::International->new('Commerce'); @@ -105,5 +436,72 @@ sub recurringPeriodValues { return \%periods; } +#------------------------------------------------------------------- + +=head2 supports + +Returns a hashref containg the types of payment the plugin supports. The hashref may contain: + + single => 1 if the plugin supports normal transactions, + recurring => 1 if the plugin supports recurring transactions + +You must override this method. + +=cut + +sub supports { + return WebGUI::ErrorHandler::fatalError("You must override the supports method in the payment plugin."); +} + +#------------------------------------------------------------------- + +=head2 transactionCompleted { + +A boolean indicating whether the payment has been finished or not. You must override this method. + +=cut + +sub transactionCompleted { + return WebGUI::ErrorHandler::fatalError("You must override the transactionCompleted method in the payment plugin."); +} + +#------------------------------------------------------------------- + +=head2 transactionError + +Returns an error message if a transaction error has occurred. You must override this method. + +=cut + +sub transactionError { + return WebGUI::ErrorHandler::fatalError("You must override the transactionError method in the payment plugin."); +} + +#------------------------------------------------------------------- + +=head2 transactionPending + +A boolean indicating whether the payment is pending or not. You must override this method. + +=cut + +sub transactionPending { + return WebGUI::ErrorHandler::fatalError("You must override the transactionPending method in the payment plugin."); +} + +#------------------------------------------------------------------- + +=head2 validateFormData + +This method checks the data entered in the checkoutForm. If an error has occurred this method must +return an arrayref containing the errormessages tied to the errors. If everything's ok it will return +undef. You must override this method. + +=cut + +sub validateFormData { + return WebGUI::ErrorHandler::fatalError("You must override the validateFormData method in the payment plugin."); +} + 1; diff --git a/lib/WebGUI/Commerce/ShoppingCart.pm b/lib/WebGUI/Commerce/ShoppingCart.pm index d9be31b03..b328c8dc5 100644 --- a/lib/WebGUI/Commerce/ShoppingCart.pm +++ b/lib/WebGUI/Commerce/ShoppingCart.pm @@ -6,7 +6,53 @@ use WebGUI::SQL; use WebGUI::Commerce::Item; use WebGUI::Commerce::Payment; +=head1 NAME + +Package WebGUI::Commerce::ShoppingCart + +=head1 DESCRIPTION + +This package implements a shopping cart for the E-Commerce system of WebGUI. This +shopping cart is tied to the sessionId and, thus, expires when the sessionId expires. + +=head1 SYNOPSIS + +$shoppingCart = WebGUI::Commerce::ShoppingCart->new; + +$shoppingCart->add('myItemId', 'myItem', 3); +$shoppingCart->empty; + +($normal, $recurring) = $shoppingCart->getItems; +$normal->[0]->{quantity} # quantity of first normal item +$recurring->[2]->{period} # period of third recurring item +$normal->[0]->{item}->id # the id of the first normal item + +=head1 METHODS + +This package provides the following methods: + +=cut + #------------------------------------------------------------------- + +=head2 add ( itemId, itemType, quantity ) + +This will add qunatity items of type itemType and with id itemId to the shopping cart. + +=head3 itemId + +The id of the item to add. + +=head3 itemType + +The type (namespace) of the item that's to be added to the cart. + +=head3 quantity + +The number of items to add. Defaults to 1 if quantity is not given. + +=cut + sub add { my ($self, $itemId, $itemType, $quantity); $self = shift; @@ -27,6 +73,13 @@ sub add { } #------------------------------------------------------------------- + +=head2 empty ( ) + +Invoking this method will putrge all content from the shopping cart. + +=cut + sub empty { my ($self); $self = shift; @@ -35,6 +88,43 @@ sub empty { } #------------------------------------------------------------------- + +=head2 getItems ( ) + +This method will return two arrayrefs repectively containing the normal items and the recurring +items in the shoppingcart. + +Items are returned as a hashref with the following properties: + +=head3 quantity + +The quantity of this item. + +=head3 period + +The duration of a billingperiod if this this is a recurring transaction. + +=head3 name + +The name of this item. + +=head3 price + +The price of a single item. + +=head3 totalPrice + +The total price of this item. Ie. totalPrice = quantity * price. + +=head3 item + +The instanciated plugin of this item. See WebGUI::Commerce::Item for a detailed API. + +For example: + + +=cut + sub getItems { my ($self, $periodResolve, %cartContent, $item, $properties, @recurring, @normal); $self = shift; @@ -63,6 +153,18 @@ sub getItems { } #------------------------------------------------------------------- + +=head2 new ( sessionId ) + +Returns a shopping cart object tied to session id sessionId or the current session. + +=head3 sessionId + +The session id this cart should be tied to. If omitted this will default to the session id +of the current user. + +=cut + sub new { my ($class, $sessionId, $sth, $row, %items); $class = shift; diff --git a/lib/WebGUI/Commerce/Transaction.pm b/lib/WebGUI/Commerce/Transaction.pm index c0a1d5be8..c118c8bf5 100644 --- a/lib/WebGUI/Commerce/Transaction.pm +++ b/lib/WebGUI/Commerce/Transaction.pm @@ -7,6 +7,21 @@ use WebGUI::SQL; use WebGUI::Commerce::Payment; #------------------------------------------------------------------- + +=head2 addItem ( item, quantity ) + +Add's an item to the transaction. + +=head3 item + +An WebGUI::Commerce::Item object of the item you want to add. + +=head3 quantity + +The number of items that are tobe added. + +=cut + sub addItem { my ($self, $item, $quantity); $self = shift; @@ -31,6 +46,14 @@ sub addItem { } #------------------------------------------------------------------- + +=head2 cancelTransaction + +Cancels a recurring transaction. This is done by trying to cancel the subscription at the gateway +using a Payment plugin. If this is succesfull the transaction is marked as canceled. + +=cut + sub cancelTransaction { my ($self, $item, $plugin); $self = shift; @@ -53,6 +76,14 @@ sub cancelTransaction { } #------------------------------------------------------------------- + +=head2 completeTransaction + +Sets the status of a transaction to 'Completed' and executes the handler for every item attached to +the transction. + +=cut + sub completeTransaction { my ($self, $item); $self = shift; @@ -66,6 +97,13 @@ sub completeTransaction { } #------------------------------------------------------------------- + +=head2 delete + +Deletes the transaction from the database; + +=cut + sub delete { my ($self) = shift; @@ -76,6 +114,17 @@ sub delete { } #------------------------------------------------------------------- + +=head2 gateway ( gatewayName ) + +Returns the gateway connected to the transaction. If gatewayName is given the gateway property is set to that. + +=head3 gatewayName + +The name to which to set the gateway. + +=cut + sub gateway { my ($self, $gateway); $self = shift; @@ -90,6 +139,17 @@ sub gateway { } #------------------------------------------------------------------- + +=head2 gatewayId ( id ) + +Returns the gateway ID of the transaction. If id is given the gateway ID is set to it. + +=head3 id + +The ID which to set the gatewayId to. + +=cut + sub gatewayId { my ($self, $gatewayId); $self = shift; @@ -104,6 +164,18 @@ sub gatewayId { } #------------------------------------------------------------------- + +=head2 get ( property ) + +Returns the property requested. If no property is specified this method returns a hashref +containing all properties. + +=head3 property + +The name of the property you want. + +=cut + sub get { my ($self, $key); $self = shift; @@ -114,6 +186,22 @@ sub get { } #------------------------------------------------------------------- + +=head2 getByGatewayId ( id, gateway ) + +Constructor. Return a transaction object that is identified by the given id and payment gateway. +Returns undef if no match is found. + +=head3 id + +The gateway ID of the transaction. + +=head3 gateway + +The payment gateway which the transaction is tied to. + +=cut + sub getByGatewayId { my ($self, $gatewayId, $paymentGateway, $transactionId); $self = shift; @@ -128,6 +216,11 @@ sub getByGatewayId { } #------------------------------------------------------------------- + +=head2 getItems + +=cut + sub getItems { my ($self); $self = shift; @@ -136,6 +229,18 @@ sub getItems { } #------------------------------------------------------------------- + +=head2 isRecurring ( recurring ) + +Returns a boolean indcating whether the transaction is recurring. If recurring is given, the isRecurring flag +will be set to it. + +=head3 recurring + +A boolean which sets the transaction as recurring if true. + +=cut + sub isRecurring { my ($self, $recurring); $self = shift; @@ -150,6 +255,17 @@ sub isRecurring { } #------------------------------------------------------------------- + +=head2 lastPayedTerm ( term ) + +Returns the last term number that has been paid. If term is given this number will be set to it. + +-head3 term + +The number which to set tha last payed term to. + +=cut + sub lastPayedTerm { my ($self, $lastPayedTerm); $self = shift; @@ -164,6 +280,26 @@ sub lastPayedTerm { } #------------------------------------------------------------------- + +=head2 new ( transactionId, [ gateway, [ userId ] ] ) + +Constructor. Returns a transaction object. If transactionId is set to 'new' a new transaction is created. + +=head3 transactionId + +The transaction ID of the transaction you want. Set to 'new' for a new transaction. + +=head3 gateway + +The payment gateway to use for this transaction. Only needed for new transactions. + +=head3 userId + +The userId of the user for whom to create this transaction. Defaults to the current user. Only optional for +new transactions. + +=cut + sub new { my ($class, $transactionId, $gatewayId, $userId, $properties, $sth, $row, @items); @@ -190,6 +326,13 @@ sub new { } #------------------------------------------------------------------- + +=head2 pendingTransactions + +Returns a reference to an array which contains transaction objects of all pending transactions. + +=cut + sub pendingTransactions { my (@transactionIds, @transactions); @transactionIds = WebGUI::SQL->buildArray("select transactionId from transaction where status = 'Pending'"); @@ -202,6 +345,17 @@ sub pendingTransactions { } #------------------------------------------------------------------- + +=head2 status ( status ) + +Returns the status of the transaction. If status is given the transaction status will be set to it. + +=head3 status + +The value to set the transaction status to. + +=cut + sub status { my ($self, $status); $self = shift; @@ -216,12 +370,30 @@ sub status { } #------------------------------------------------------------------- + +=head2 transactionId + +Returns the transactionId of the transaction. + +=cut + sub transactionId { my $self = shift; return $self->{_transactionId}; } #------------------------------------------------------------------- + +=head2 transactionsByUser ( userId ) + +Returns a reference to an array containing transaction objects of all tranactions by the user corresponding to userId. + +=head3 userId + +The ID of the user you want the transaction of. + +=cut + sub transactionsByUser { my ($self, @transactionIds, @transactions, $userId); my $self = shift;