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
|