467 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			467 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| # ====================================================================
 | |
| # Gossamer Threads Module Library - http://gossamer-threads.com/
 | |
| #
 | |
| #   GT::Payment::Remote::WorldPay
 | |
| #   Author: Jason Rhinelander
 | |
| #   CVS Info : 087,071,086,086,085      
 | |
| #   $Id: WorldPay.pm,v 1.9 2006/08/22 23:03:14 brewt Exp $
 | |
| #
 | |
| # Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | |
| # ====================================================================
 | |
| #
 | |
| # Description:
 | |
| #   WorldPay "Select Junior" payment processing.
 | |
| #
 | |
| #
 | |
| # One major shortcoming of WorldPay is that its callback system is quite weak.
 | |
| # It won't try to inform you very hard - it tries once, but if it doesn't
 | |
| # connect it gives up and doesn't try again, making it entirely possible and
 | |
| # likely that you will have to manually add missing payments at some point.
 | |
| #
 | |
| 
 | |
| package GT::Payment::Remote::WorldPay;
 | |
| use strict;
 | |
| use Carp;
 | |
| require Exporter;
 | |
| use vars qw/@ISA @EXPORT_OK/;
 | |
| @ISA = qw/Exporter/;
 | |
| @EXPORT_OK = qw/process md5_signature/;
 | |
| 
 | |
| sub process {
 | |
| # -----------------------------------------------------------------------------
 | |
|     shift if $_[0] and UNIVERSAL::isa($_[0], __PACKAGE__);
 | |
| 
 | |
|     my %opts = @_;
 | |
|     $opts{param} and UNIVERSAL::isa($opts{param}, 'GT::CGI') or croak 'Usage: ->process(param => $gtcgi, ...)';
 | |
|     my $in = $opts{param};
 | |
| 
 | |
|     ref $opts{on_valid} eq 'CODE'
 | |
|         or ref $opts{on_recurring} eq 'CODE'
 | |
|         or croak 'Usage: ->process(on_valid => \&CODEREF, ...)';
 | |
| 
 | |
|     defined $opts{password} and length $opts{password} or croak 'Usage: ->process(password => "password", ...)';
 | |
| 
 | |
|     for (qw/on_valid on_recurring on_cancel on_invalid_password on_recurring_failed on_recurring_cancelled/) {
 | |
|         !$opts{$_} or ref $opts{$_} eq 'CODE' or croak "Usage: ->process($_ => \\&CODEREF, ...)";
 | |
|     }
 | |
| 
 | |
|     my $callbackpw = $in->param('callbackPW');
 | |
|     unless ($callbackpw and $callbackpw eq $opts{password}) {
 | |
|         $opts{on_invalid_password}->() if $opts{on_invalid_password};
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     my $trans_status = $in->param('transStatus');
 | |
| 
 | |
| # The transaction was a testMode transaction, but testMode is not enabled.
 | |
|     if ($in->param('testMode') and not $opts{test_mode}) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if ($in->param('futurePayId')) {
 | |
|         if ($trans_status eq 'Y') {
 | |
|             $opts{on_recurring}->() if $opts{on_recurring};
 | |
|         }
 | |
|         elsif ($trans_status eq 'N') {
 | |
|             $opts{on_recurring_failed}->() if $opts{on_recurring_failed};
 | |
|         }
 | |
|         elsif ($in->param('futurePayStatusChange') eq 'Customer Cancelled') {
 | |
|             $opts{on_recurring_cancelled}->() if $opts{on_recurring_cancelled};
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         if    (uc $trans_status eq 'Y') { $opts{on_valid}->()  if $opts{on_valid}  }
 | |
|         elsif (uc $trans_status eq 'C') { $opts{on_cancel}->() if $opts{on_cancel} }
 | |
|     }
 | |
| 
 | |
|     return;
 | |
| }
 | |
| 
 | |
| sub md5_signature {
 | |
| # -----------------------------------------------------------------------------
 | |
|     shift if $_[0] and UNIVERSAL::isa($_[0], __PACKAGE__);
 | |
|     require GT::MD5;
 | |
|     return GT::MD5::md5_hex(join ":", @_);
 | |
| }
 | |
| 
 | |
| 1;
 | |
| 
 | |
| __END__
 | |
| 
 | |
| =head1 NAME
 | |
| 
 | |
| GT::Payment::Remote::WorldPay - WorldPay payment handling
 | |
| 
 | |
| =head1 CAVEATS
 | |
| 
 | |
| One thing to note about WorldPay is that its security system is a little weak -
 | |
| you can't trust a callback post as actually being genuine, unless you use the
 | |
| callback password feature - and even at that it is not a terribly secure
 | |
| solution.  In this regard, other payment provides have much cleaner transaction
 | |
| systems.  Another shortcoming of WorldPay is that its callback system is
 | |
| somewhat weak - it won't try to inform you very hard: it tries once, but if it
 | |
| doesn't connect it gives up and doesn't try again, making it entirely possible
 | |
| and likely that you will have to manually add (or confirm) missing payments at
 | |
| some point, so supporting at least manual payment approval of initiated
 | |
| payments is absolutely required.
 | |
| 
 | |
| =head1 SYNOPSIS
 | |
| 
 | |
|     use GT::Payment::Remote::WorldPay;
 | |
|     use GT::CGI;
 | |
| 
 | |
|     my $in = new GT::CGI;
 | |
| 
 | |
|     GT::Payment::Remote::WorldPay->process(
 | |
|         param => $in,
 | |
|         on_valid => \&valid,
 | |
|         on_cancel => \&cancel,
 | |
| 
 | |
|         on_recurring => \&recurring,
 | |
|         on_recurring_failed => \&recurring_failed,
 | |
|         on_recurring_cancelled => \&recurring_cancelled,
 | |
| 
 | |
|         password => "123",
 | |
|         on_invalid_password => \&invalid_pw
 | |
|     );
 | |
| 
 | |
|     sub valid {
 | |
|         # Update database - the payment has been made successfully.
 | |
|     }
 | |
| 
 | |
|     sub cancel {
 | |
|         # Update database - the user has clicked the "Cancel" button, thereby
 | |
|         # cancelling the payment.  You should take note of the cancellation.
 | |
|     }
 | |
| 
 | |
|     sub on_recurring {
 | |
|         # Update database - a recurring payment has been made successfully.
 | |
|     }
 | |
| 
 | |
|     sub on_recurring_failed {
 | |
|         # Update database - a recurring payment has failed.
 | |
|     }
 | |
| 
 | |
|     sub on_recurring_cancelled {
 | |
|         # Update database - either the customer or the merchant has cancelled
 | |
|         # this recurring payment
 | |
|     }
 | |
| 
 | |
|     sub on_invalid_password {
 | |
|         # Perhaps make a record - a payment callback was received without a
 | |
|         # valid password
 | |
|     }
 | |
| 
 | |
| =head1 DESCRIPTION
 | |
| 
 | |
| This module is designed to handle WorldPay payment processing using WorldPay's
 | |
| "Select Junior" system and callback.
 | |
| 
 | |
| =head1 REQUIREMENTS
 | |
| 
 | |
| GT::CGI is the only requirement, however GT::MD5 is required in order to use
 | |
| the md5_signature function.
 | |
| 
 | |
| =head1 FUNCTIONS
 | |
| 
 | |
| This module has only two functions.  process() does the work of actually
 | |
| figuring out what to do with a postback, and md5_signature() is used to
 | |
| generate an MD5 signature for payment verification and security purposes.  Both
 | |
| functions can be imported into your package, and can be called as either method
 | |
| or function.
 | |
| 
 | |
| =head2 process
 | |
| 
 | |
| process() is the main function provided by this module.  It can be called as
 | |
| either a function or class method, and takes a hash (not hash reference) of
 | |
| arguments as described below.
 | |
| 
 | |
| process() should be called for WorldPay initiated postbacks.  This can be set
 | |
| up in your main CGI by looking for WorldPay-specific CGI parameters
 | |
| ('transStatus' is a good one to look for) or by making a seperate .cgi file
 | |
| exclusively for handling WorldPay postbacks.
 | |
| 
 | |
| Additionally, it is strongly advised that database connection, authenticate,
 | |
| etc.  be performed before calling process() to ensure that the payment is
 | |
| recorded successfully.  WorldPay will not attempt to repost the form data if
 | |
| your script produces an error, and the error will be shown to the customer.
 | |
| 
 | |
| The L<C<param>|/"param"> argument, either L<C<on_valid>|/"on_valid"> or
 | |
| L<C<on_recurring>|/"on_recurring">, and the L<C<password>|/"password"> options
 | |
| are required.  Using L<MD5 signing|/"MD5 signing"> as well is strongly advised.
 | |
| 
 | |
| =over 4
 | |
| 
 | |
| =item param
 | |
| 
 | |
| param takes a GT::CGI object from which WorldPay postback variables are read.
 | |
| 
 | |
| =item on_valid
 | |
| 
 | |
| on_valid takes a code reference as value.  The code reference will be called
 | |
| when a successful payment has been made.  Inside this code reference you are
 | |
| responsible for setting a "paid" status for the order in question.
 | |
| 
 | |
| =item on_cancel
 | |
| 
 | |
| Takes a code reference to call in the event of the customer clicking the
 | |
| "cancel" button.  Note that this is not sent if the user closes their browser,
 | |
| but only if they click "cancel."
 | |
| 
 | |
| You should record a cancelled payment in your application.
 | |
| 
 | |
| =item password
 | |
| 
 | |
| This is a password that the customer should set in the WorldPay Customer
 | |
| Management System, and provide to you.  Without this password, WorldPay
 | |
| postbacks should not be considered secure.
 | |
| 
 | |
| =item on_invalid_password
 | |
| 
 | |
| This code reference will be called when the correct password is not present in
 | |
| the postback request.  This will also be called if no password is provided.
 | |
| 
 | |
| =item on_recurring
 | |
| 
 | |
| =item on_recurring_failed
 | |
| 
 | |
| =item on_recurring_cancelled
 | |
| 
 | |
| In order to support recurring payments, you must at least define
 | |
| C<on_recurring>.  C<on_recurring> is called when a successful recurring payment
 | |
| has been made.  C<on_recurring_failed> is called for a failed recurring payment
 | |
| (e.g. credit card declined).  See
 | |
| L<the Recurring charges section|/"Recurring charges"> for more details.
 | |
| 
 | |
| Bear in mind that if you do not set up the on_recurring callback, recurring
 | |
| payments will be ignored.
 | |
| 
 | |
| =back
 | |
| 
 | |
| =head2 md5_signature
 | |
| 
 | |
| The md5_signature() function takes a password (this must be set for the
 | |
| WorldPay account), and a list of values and generates an appropriate WorldPay
 | |
| MD5 signature, which should be included as the "signature" field.  See
 | |
| L<the MD5 signing section|/"MD5 signing"> for more details.
 | |
| 
 | |
| =head1 INSTRUCTIONS
 | |
| 
 | |
| To implement WorldPay payment processing, there are a number of steps required
 | |
| in addition to this module.  Basically, this module handles only the postback
 | |
| stage of the WorldPay payment process.
 | |
| 
 | |
| Full WorldPay "Select Junior" information is available from the "Select Junior
 | |
| Integration Guide" available from www.worldpay.com.
 | |
| 
 | |
| =head2 Directing customers to WorldPay
 | |
| 
 | |
| This is done by creating a web form containing the following variables.  Your
 | |
| form, first of all, must make a C<post> request to
 | |
| C<https://select.worldpay.com/wcc/purchase>.
 | |
| 
 | |
| Required fields are as follows:
 | |
| 
 | |
| =over 4
 | |
| 
 | |
| =item instId
 | |
| 
 | |
| Your WorldPay Installation ID.  Example: C<1234>
 | |
| 
 | |
| =item currency
 | |
| 
 | |
| The currency of the purchase.  Example: C<GBP>
 | |
| 
 | |
| =item desc
 | |
| 
 | |
| A description of the purchase.  Example: C<Blue T-Shirt, Medium>
 | |
| 
 | |
| =item cartId
 | |
| 
 | |
| A reference you assign to help you identify the purchase.  Example: C<10a0491>.
 | |
| 
 | |
| =item amount
 | |
| 
 | |
| The total cost of the purchase.  Example: C<25.35>
 | |
| 
 | |
| =back
 | |
| 
 | |
| =head2 Recurring charges
 | |
| 
 | |
| Additionally, in order to set up recurring payments, the WorldPay account must
 | |
| have "FuturePay" enabled, and then you need to use the following parameters.
 | |
| 
 | |
| The below parameters are used for the "Regular FuturePay Agreements" - there is
 | |
| also "Limited FuturePay Agreements" in which a maximum overall charge is set.
 | |
| For more information, see L<Repear Billing With FuturePay|/"SEE ALSO">.
 | |
| 
 | |
| =over 4
 | |
| 
 | |
| =item futurePayType
 | |
| 
 | |
| Should contain the value "regular", unless using "Limited FuturePay Agreements,"
 | |
| which will work but is not described here.
 | |
| 
 | |
| =item option
 | |
| 
 | |
| Should contain either 0, 1, or 2.  0 means the payment amount is fixed and
 | |
| cannot be changed.  1 means the payment is fixed, but can be changed to another
 | |
| amount at any point.  2 means the payment amount must be set before each
 | |
| recurring payment.
 | |
| 
 | |
| =item startDate
 | |
| 
 | |
| Value in the format: "yyyy-mm-dd".  This should be the date on which the first
 | |
| future payment should be taken.  Note that this is _NOT_ and CANNOT be today,
 | |
| but must be a value in the future.  If using option 2, this value must be at
 | |
| least 2 weeks in the future.
 | |
| 
 | |
| =item startDelayUnit
 | |
| 
 | |
| One digit: 1: day, 2: week, 3: month, 4: year.  Only used if startDate is
 | |
| B<not> set.  If using option 2, this value must be at least 2 weeks in the
 | |
| future.
 | |
| 
 | |
| =item startDelayMult
 | |
| 
 | |
| The actual delay is obtained by multiplying this value by startDelayUnit.  So,
 | |
| to start in three weeks, this would be "3", and startDelayUnit would be "2".
 | |
| Again, this is not used if startDate is specified.  Must be >= 1 if set.
 | |
| 
 | |
| =item noOfPayments
 | |
| 
 | |
| This number of payments that will be made.  Leave as 0 or unset for unlimited.
 | |
| 
 | |
| =item intervalUnit
 | |
| 
 | |
| One digit: 1: day, 2: week, 3: month, 4: year.  The unit of interval between
 | |
| payments.  This must be set unless noOfPayments is 1.  If using option 1 or
 | |
| option 2, the minimum interval is 2 weeks.
 | |
| 
 | |
| =item intervalMult
 | |
| 
 | |
| The interval between payments is determined by this value multiplied by
 | |
| intervalUnit.  So, to make payments every 1 month, this would be "1", and
 | |
| intervalUnit would be "3".  Must be >= 1.
 | |
| 
 | |
| =item normalAmount
 | |
| 
 | |
| This must be set for option 0 and option 1, but cannot be set for option 2.
 | |
| 
 | |
| =item initialAmount
 | |
| 
 | |
| This can be used for option 0 or option 1, but cannot be set for option 2.  If
 | |
| set, this overrides the amount of the first payment.
 | |
| 
 | |
| =back
 | |
| 
 | |
| For FuturePay (recurring) payments, you still pass the required fields as
 | |
| normal, except for the amount field: amount can be passed as 0 or a value - if
 | |
| a value is specified, this will be treated as an immediate payment.  So, for
 | |
| example, if you wanted to charge someone a monthly subscription of $10 starting
 | |
| today you would pass the following variables:
 | |
| 
 | |
|     instId=1234 # (the merchant's installation reference here)
 | |
|     amount=10
 | |
|     cartId=8456a9264q314 # (Some random ID here that you generate)
 | |
|     currency=USD # (Whatever currency they are charging in goes here)
 | |
|     desc=Subscription For Something Cool # (Description of subscription)
 | |
|     option=0
 | |
|     normalAmount=10
 | |
|     startDelayUnit=3
 | |
|     startDelayMult=1
 | |
|     intervalUnit=3
 | |
|     intervalMult=1
 | |
| 
 | |
| =head2 MD5 signing
 | |
| 
 | |
| Additionally, using WorldPay's MD5 signature feature is strongly recommended.
 | |
| 
 | |
| To enable this feature, provide a field "signatureFields", containing fields
 | |
| separated by ":".  Although any fields can be used, "amount:currency:cartId" is
 | |
| recommended.  Then, call:
 | |
| 
 | |
|     my $md5 = GT::Payment::Remote::WorldPay::md5_signature(
 | |
|         $password, $amount, $currency, $cartId
 | |
|     );
 | |
| 
 | |
| $password should be a password provided by the user and known only to the user
 | |
| and WorldPay.  The value returned should be passed as the "signature" variable.
 | |
| 
 | |
| This MD5 protection causes WorldPay to reject any faked payment requests and so
 | |
| is reasonably secure.
 | |
| 
 | |
| =head2 Postback
 | |
| 
 | |
| Before WorldPay postback notification can occur, you must instruct the user to
 | |
| enable the callback facility in the Customer Management System.  Additionally,
 | |
| it is recommended that a proper URL to your CGI be specified there, or else
 | |
| pass along a "MC_callback" variable that points to the script _WITHOUT_ a
 | |
| leading http:// or https://. (e.g. MC_callback=www.example.com/callback.cgi).
 | |
| 
 | |
| Note that a WorldPay limitation prevents the callback protocol (http://) from
 | |
| being changed dynamically - whatever protocol is set for your callback URL in
 | |
| the Customer Management System will be used with the dynamic callback URL.
 | |
| 
 | |
| =head2 Putting it all together
 | |
| 
 | |
| The typical way to implement all of this is as follows:
 | |
| 
 | |
| =over 4
 | |
| 
 | |
| =item 1 Get necessary merchant information (instId, currency, callback
 | |
| password, and MD5 password).
 | |
| 
 | |
| =item 2 Once the customer has selected what to purchase, generate a cartId (a
 | |
| random MD5 hex string works well - but I<do not> use the MD5 signature!), and
 | |
| L<generate the MD5 signature|/"MD5 signing">.
 | |
| 
 | |
| =item 3 Store the cartId somewhere (i.e. in the database).
 | |
| 
 | |
| =item 4 Make a form with all the necessary fields that
 | |
| L<submits to WorldPay|/"Directing customers to WorldPay">.
 | |
| 
 | |
| =item 5 Set up the necessary callbacks (at least L<C<on_valid>|/"on_valid"> and
 | |
| L<C<on_valid>|/"on_cancel">).  If using a dedicated CGI script for WorldPay
 | |
| callbacks, it should just call process(); otherwise, check for the CGI
 | |
| parameter 'transStatus' and if present, call process().
 | |
| 
 | |
| =item 6 For a valid payment, do whatever you need to do for a valid payment,
 | |
| and store some record of the payment having been made (storing at least the
 | |
| cartId, the transId, and the futurePayId is strongly recommended).  Use the CGI
 | |
| parameter 'cartId' to locate the order (i.e. in the database).  It's
 | |
| recommended that you check Appendix A of the "Select Junior Integration Guide"
 | |
| for all available parameters.
 | |
| 
 | |
| =back
 | |
| 
 | |
| =head1 SEE ALSO
 | |
| 
 | |
| L<http://support.worldpay.com> - WorldPay Knowledge Base, containing many
 | |
| useful WorldPay manuals and instructions.
 | |
| 
 | |
| L<http://support.worldpay.com/kb/integration_guides/junior/integration/help/sjig.html>
 | |
| - Select Junior Integration Guide, from which this documentation and module is
 | |
| primarily derived.
 | |
| 
 | |
| L<http://support.worldpay.com/kb/product_guides/futurepay/repeatbilling.html> -
 | |
| Repeat Billing with FuturePay.
 | |
| 
 | |
| =head1 MAINTAINER
 | |
| 
 | |
| Jason Rhinelander
 | |
| 
 | |
| =head1 COPYRIGHT
 | |
| 
 | |
| Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | |
| http://www.gossamer-threads.com/
 | |
| 
 | |
| =head1 VERSION
 | |
| 
 | |
| Revision: $Id: WorldPay.pm,v 1.9 2006/08/22 23:03:14 brewt Exp $
 | |
| 
 | |
| This module is designed for version 4.4 of the Select Junior payment
 | |
| integration.
 | |
| 
 | |
| =cut
 | 
