First pass at adding key files

This commit is contained in:
dsainty
2024-06-17 21:49:12 +10:00
commit aa25e9347f
1274 changed files with 392549 additions and 0 deletions

View File

@ -0,0 +1,317 @@
# ====================================================================
# Gossamer Threads Module Library - http://gossamer-threads.com/
#
# GT::Payment::Remote::2CheckOut
# Author: Jason Rhinelander
# CVS Info : 087,071,086,086,085
# $Id: 2CheckOut.pm,v 1.5 2006/08/22 20:39:04 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# ====================================================================
#
# Description:
# 2CheckOut payment processing.
#
package GT::Payment::Remote::2CheckOut;
use strict;
use Carp;
use GT::MD5 'md5_hex';
require Exporter;
use vars qw/@EXPORT_OK/;
@EXPORT_OK = qw/process/;
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 croak 'Usage: ->process(on_valid => \&CODEREF, ...)';
defined $opts{password} and length $opts{password} or croak 'Usage: ->process(password => "password", ...)';
defined $opts{sellerid} and length $opts{sellerid} or croak 'Usage: ->process(sellerid => "sellerid", ...)';
$opts{password} eq 'tango' and croak 'Usage: ->process(password => "something other than \'tango\'", ...)';
my $order_number = $in->param('order_number');
# Check that the "secret word" (password) combined with the other information
# actually checks out.
my $str = $opts{password} . $opts{sellerid} . $order_number . $in->param('total');
my $md5 = md5_hex($str);
if (lc $md5 eq lc $in->param('key')) {
$opts{on_valid}->();
}
# If demo mode is enabled, then the order number is set to 1 in the md5:
# https://www.2checkout.com/documentation/UsersGuide2/chapter6/md5-hash.html
elsif ($opts{demo}) {
$str = $opts{password} . $opts{sellerid} . 1 . $in->param('total');
$md5 = md5_hex($str);
if (lc $md5 eq lc $in->param('key')) {
$opts{on_valid}->();
}
}
return;
}
1;
__END__
=head1 NAME
GT::Payment::Remote::2CheckOut - 2CheckOut payment handling
=head1 CAVEATS
2CheckOut has a pretty weak automated payment system - the security of the
entire automated payment process hinges on your "Secret Word" (Admin -> Account
Details -> Return -> Secret Word (near the bottom of the page)) - without it,
there is no security at all. Another weakness in the system is that if your
server is not reachable for whatever reason, the payment information would be
lost. Payment providers like 2CheckOut and WorldPay would do well to learn
from payment systems like that of PayPal - whatever can be said about other
aspects of PayPal, they do have one of the nicest payment systems around - both
from a developer and user's point of view.
Because of the security issue with not using the "Secret Word", this module
requires that the secret word be used, even if other 2CheckOut systems may not.
Additionally, the default secret word of "tango" is not allowed.
=head1 SYNOPSIS
use GT::Payment::Remote::2CheckOut;
use GT::CGI;
my $in = new GT::CGI;
GT::Payment::Remote::2CheckOut->process(
param => $in,
on_valid => \&valid,
sellerid => "1234",
password => "Some Good Secret Word"
);
sub valid {
# Update database - the payment has been made successfully.
}
=head1 DESCRIPTION
This module is designed to handle 2CheckOut payment processing.
=head1 REQUIREMENTS
GT::CGI and GT::MD5.
=head1 FUNCTIONS
This module has only one function: process() does the work of actually
figuring out what to do with a postback.
=head2 process
process() is the only 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 2CheckOut initiated postbacks. This can be set
up in your main .cgi by looking for 2CheckOut-specific CGI parameters
('cart_order_id' is a good one to look for) or by making a seperate .cgi file
exclusively for handling 2CheckOut postbacks.
Additionally, it is strongly advised that database connection, authenticate,
etc. be performed before calling process() to ensure that the payment is
recorded successfully. 2CheckOut will not attempt to repost the form data if
your script produces an error, and the error will be shown to the customer.
=over 4
=item param
param takes a GT::CGI object from which 2CheckOut 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. The
C<cart_order_id> CGI variable will have whatever cart_order_id you provided.
=item sellerid
This should be passed to seller number. This is needed, along with the
password field below, to verify that the posted payment is a genuine 2CheckOut
payment.
=item password
This is a "Secret Word" that the admin must set in the 2CheckOut admin area
(under Look & Feel -> Secret Word). This field must be set in the admin, and
passed in here. Note that the default value, "tango", is not allowed. Without
this password, 2CheckOut postbacks should not be considered secure.
=item demo
Whether or not to initiate and accept demo transactions.
=back
=head1 INSTRUCTIONS
To implement 2CheckOut payment processing, there are a number of steps required
in addition to this module. Basically, this module handles only the postback
stage of the 2CheckOut payment process.
=head2 Directing customers to 2CheckOut
This is done by creating a web form containing the following variables. Your
form, first of all, should post to
C<https://www.2checkout.com/2co/buyer/purchase>. See
C<https://www.2checkout.com/documentation/UsersGuide2/third_party_carts/2co-system-parameters.html>
for a complete and up-to-date list of parameters that can be passed to 2CheckOut.
Required fields are as follows:
=over 4
=item * sid
Your 2CheckOut account number
=item * total
The total amount to be billed, in DD.CC format.
=item * cart_order_id
A unique order id, which you should store to track the payment.
=back
The following parameters *may* be passed in, and will be available in the
postback:
=over 4
=item * card_holder_name
=item * street_address
=item * city
=item * state
=item * zip
=item * country
=item * phone
The card holder's details.
=item * email
The card holder's email address.
=item * ship_name
=item * ship_street_address
=item * ship_city
=item * ship_state
=item * ship_zip
=item * ship_country
Shipping info - however, according to 2CheckOut, you must indicate that you
want to take that you want to take down a seperate shipping and billing address
on the L<Shipping Details page|https://sellers.2checkout.com/cgi-bin/sellersarea/shipdetails.2c>.
=item * demo
Should be set to 'Y' if you want demo mode, omitted for regular transactions.
=back
In the postback CGI, you'll get back all of the billing and shipping variables
listed above, plus:
=over 4
=item * order_number
2CheckOut order number
=item * cart_order_id
=item * cart_id
Your order number, passed back. Both variables are the same.
=back
=head2 Postback
Before 2CheckOut postback notification can occur, you must set up the postback
(in 2CheckOut terminology, "Routine"). This can be set from the Admin ->
Shopping Cart -> Cart Details. You need to enable the payment routine, and
set it to a CGI that you manage.
=head2 Putting it all together
The typical way to implement all of this is as follows:
=over 4
=item 1 Get necessary merchant information (sid and secret keyword)
=item 2 Once the customer has selected what to purchase, generate a
cart_order_id (a random MD5 hex string works well), and store it somewhere
(i.e. in the database).
=item 3 Make a form with all the necessary fields that
L<submits to 2CheckOut|/"Directing customers to 2CheckOut">.
=item 4 Set up the L<C<on_valid>|/"on_valid"> callback. If using a dedicated
CGI script for 2CheckOut callbacks, it should just call process(); otherwise,
check for the CGI parameter 'cart_order_id' and if present, call process().
=item 5 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
cart_order_id and the order_number is strongly recommended). Use the CGI
parameter 'cart_order_id' to locate the order (i.e. in the database).
=back
=head1 SEE ALSO
L<http://www.2checkout.com> - 2CheckOut website.
L<http://www.support.2checkout.com/deskpro/faq.php> - 2CheckOut knowledgebase
=head1 MAINTAINER
Jason Rhinelander
=head1 COPYRIGHT
Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
http://www.gossamer-threads.com/
=head1 VERSION
Revision: $Id: 2CheckOut.pm,v 1.5 2006/08/22 20:39:04 brewt Exp $
=cut

View File

@ -0,0 +1,573 @@
# ====================================================================
# Gossamer Threads Module Library - http://gossamer-threads.com/
#
# GT::Payment::Remote::PayPal
# Author: Jason Rhinelander
# CVS Info : 087,071,086,086,085
# $Id: PayPal.pm,v 1.8 2006/04/08 03:42:05 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
# ====================================================================
#
# Description:
# PayPal IPN payment processing.
# IPN information: (PayPal login required)
# https://www.paypal.com/cgi-bin/webscr?cmd=p/acc/ipn-info
#
# Net::SSLeay is required. Windows (ActivePerl) Net::SSLeay packages are
# available through Gossamer Threads.
#
package GT::Payment::Remote::PayPal;
use strict;
use Carp;
use GT::WWW;
use GT::WWW::https;
# Usage:
# process(
# param => $GT_CGI_OBJ,
# on_valid => \&CODEREF, # Called when everything checks out
# on_pending => \&CODEREF, # Optional - another IPN request will come in when no longer pending
# on_failed => \&CODEREF, # "The payment has failed. This will only happen if the payment was made from your customer's bank account"
# on_denied => \&CODEREF, # "You, the merchant, denied the payment. This will only happen if the payment was previously pending due to one of the "pending reasons" below"
# on_invalid => \&CODEREF, # This request did NOT come from PayPal
# on_recurring => \&CODEREF, # A recurring payment
# on_recurring_signup => \&CODEREF, # A recurring payment signup
# on_recurring_cancel => \&CODEREF, # A recurring payment cancellation
# on_recurring_failed => \&CODEREF, # A subscription payment failure
# on_recurring_eot => \&CODEREF, # A subscription "end of term" notification
# on_recurring_modify => \&CODEREF, # A subscription modification notification
# duplicate => \&CODEREF, # Check to make sure this isn't a duplicate (1 = okay, 0/undef = duplicate)
# email => \&CODEREF, # Called with the specified e-mail - check it against the primary e-mail account, return 1 for valid, 0/undef for error
# on_error => \&CODEREF # Optional
# )
# Only on_error is optional. on_valid will be called if the request is valid,
# on_invalid is invalid, and on_error if an error occurs (such as an HTTP error,
# connection problem, etc.)
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};
for (qw/on_valid on_failed on_denied duplicate email/) {
ref $opts{$_} eq 'CODE' or croak "Usage: ->process($_ => \&CODEREF, ...)";
}
for (qw/on_error on_pending on_invalid on_recurring on_recurring_signup on_recurring_cancel
on_recurring_failed on_recurring_eot on_recurring_modify/) {
!$opts{$_} or ref $opts{$_} eq 'CODE' or croak "Usage: ->process($_ => \\&CODEREF, ...) (optional)";
}
my $sandbox = $opts{sandbox} ? 'sandbox.' : '';
my $wwws = GT::WWW->new("https://www.${sandbox}paypal.com/cgi-bin/webscr");
my @param;
for my $p ($in->param) {
for my $v ($in->param($p)) {
push @param, $p, $v;
}
}
# PayPal says:
# You will also need to append a variable named "cmd" with the value
# "_notify-validate" (e.g. cmd=_notify-validate) to the POST string.
$wwws->parameters(@param, cmd => '_notify-validate');
my $result = $wwws->post;
my $status;
# PayPal says:
# PayPal will respond to the post with a single word, "VERIFIED" or
# "INVALID", in the body of the response. When you receive a VERIFIED
# response, you need to:
#
# * Check that the "payment_status" is "completed"
# * If the "payment_status" is "completed", check the "txn_id" against
# the previous PayPal transaction you have processed to ensure it is
# not a duplicate.
# * After you have checked the "payment_status" and "txn_id", make sure
# the "receiver_email" is an email address registered in your PayPal
# account
# * Once you have completed the above checks, you may update your
# database based on the information provided.
if ($result) {
my $status = "$result";
unless ($status eq 'VERIFIED') {
$opts{on_invalid}->($status) if $opts{on_invalid};
return;
}
# For certain txn_types payment_status and txn_id aren't available
my $txn_type = $in->param('txn_type');
if ($txn_type =~ /^subscr_(?:signup|cancel|failed|eot|modify)$/) {
if ($txn_type eq 'subscr_signup') {
$opts{on_recurring_signup}->() if $opts{on_recurring_signup};
}
elsif ($txn_type eq 'subscr_cancel') {
$opts{on_recurring_cancel}->() if $opts{on_recurring_cancel};
}
elsif ($txn_type eq 'subscr_failed') {
$opts{on_recurring_failed}->() if $opts{on_recurring_failed};
}
elsif ($txn_type eq 'substr_eot') {
$opts{on_recurring_eot}->() if $opts{on_recurring_eot};
}
elsif ($txn_type eq 'substr_modify') {
$opts{on_recurring_modify}->() if $opts{on_recurring_modify};
}
return;
}
# * Check that the "payment_status" is "completed" [sic; should be "Completed"]
unless ((my $status = $in->param('payment_status')) eq 'Completed') {
if ($status eq 'Pending') {
$opts{on_pending}->() if $opts{on_pending};
}
elsif ($status eq 'Failed') {
$opts{on_failed}->();
}
elsif ($status eq 'Denied') {
$opts{on_denied}->();
}
elsif ($status eq 'Refunded') {
$opts{on_refund}->() if $opts{on_refund};
}
elsif ($opts{on_error}) {
$opts{on_error}->("PayPal sent invalid/unknown payment_status value: '$status'");
}
return;
}
my $txn_id = $in->param('txn_id');
return unless $txn_id;
# * If the "payment_status" is "completed", check the "txn_id" against
# the previous PayPal transaction you have processed to ensure it is
# not a duplicate.
$opts{duplicate}->($txn_id) or return;
# * After you have checked the "payment_status" and "txn_id", make sure
# the "receiver_email" is an email address registered in your PayPal
# account
$opts{email}->($in->param('receiver_email')) or return; # Ignore if the e-mail addresses don't match
if ($txn_type eq 'subscr_payment') {
$opts{on_recurring}->() if $opts{on_recurring};
}
else {
$opts{on_valid}->();
}
}
elsif ($opts{on_error}) {
if (defined $result) {
my $http_status = $result->status;
$opts{on_error}->("Server returned a non-okay status: " . int($http_status) . " $http_status");
}
else {
$opts{on_error}->("Connection error: " . $wwws->error);
}
}
return;
}
1;
__END__
=head1 NAME
GT::Payment::Remote::PayPal - PayPal payment handling
=head1 SYNOPSIS
use GT::Payment::Remote::PayPal;
use GT::CGI;
my $in = new GT::CGI;
GT::Payment::Remote::PayPal->process(
param => $in,
on_valid => \&valid,
on_pending => \&pending,
on_failed => \&failed,
on_denied => \&denied,
on_invalid => \&invalid,
on_recurring => \&recurring,
on_recurring_signup => \&r_signup,
on_recurring_cancel => \&r_cancel,
on_recurring_failed => \&r_failed,
on_recurring_eot => \&r_eot,
on_recurring_modify => \&r_modify,
duplicate => \&duplicate,
email => \&email,
on_error => \&error
);
sub valid {
# Update database - the payment has been made successfully.
}
sub pending {
# Optional; store a "payment pending" status if you wish. This is optional
# because another postback will be made with a completed, failed, or denied
# status.
}
failed {
# According to PayPal IPN documentation: "The payment has failed. This
# will only happen if the payment was made from your customer's bank
# account."
# Store a "payment failed" status for the order
}
sub denied {
# According to PayPal IPN documentation: "You, the merchant, denied the
# payment. This will only happen if the payment was previously pending due
# to one of the "pending reasons" [in pending_reason]"
}
sub invalid {
# This means the request did NOT come from PayPal. You should log the
# request for follow up.
}
sub recurring {
# This means a recurring payment has been made successfully. Update
# database.
}
sub r_signup {
# This means a recurring signup has been made (NOT a payment, just a
# signup).
}
sub r_cancel {
# The user has cancelled their recurring payment
}
sub r_failed {
# A recurring payment has failed (probably declined).
}
sub r_eot {
# A recurring payment has come to its natural conclusion. This only
# applies to payments with a set number of payments.
}
sub r_modify {
# Something has been modified regarding the recurring payment
}
sub duplicate {
# Check to see if the payment has already been made. If it _has_ been
# made, you should return undef, otherwise return 1 to indicate that this
# is not a duplicate postback. The "txn_id" value is passed in, but is
# also available through $in->param('txn_id').
}
sub email {
# This will be called with an e-mail address. You should check to make
# sure that the e-mail address entered is the same as the one on the PayPal
# account. Return true (1) if everything checks out, undef otherwise.
}
sub error {
# An error message is passed in here. This is called when a error such as
# a connection problem or HTTP problem occurs.
}
=head1 DESCRIPTION
This module is designed to handle PayPal payment processing using PayPal's IPN
system. It does very little other than generating and sending a proper
response to the PayPal server, and calling the provided code reference(s).
It is strongly recommended that you familiarize yourself with the PayPal
"Single Item Purchases Manual" and "IPN Manual" listed in the L</"SEE ALSO">
section of this document.
=head1 REQUIREMENTS
GT::WWW with the https protocol, which in turn requires Net::SSLeay. PPM's are
available from Gossamer Threads for the latest Windows releases of ActiveState
Perl 5.6.1 and 5.8.0.
=head1 process
process() is the only function/method 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. This module requires GT::WWW's
https interface, which in turn requires Net::SSLeay.
process() should be called for PayPal initiated requests. This can be set up
in your main CGI by looking for PayPal-specific CGI parameters ('txn_type' is a
good one to look for) or by making a seperate .cgi file exclusively for
handling IPN postbacks.
Additionally, it is strongly advised that database connection, authenticate,
etc. be performed before calling process() to ensure that the payment is
recorded successfully. If your CGI script has an error, PayPal will retry the
postback again
Except where indicated, all arguments are required.
=head2 param
param takes a GT::CGI object from which PayPal IPN variables are read.
=head2 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.
See the PayPal IPN documentation listed below for information on how to
identify an order.
=head2 on_pending
on_pending is called when PayPal sends information on a "Pending" payment.
This parameter is optional, due to the fact that a "Pending" status means that
another notification (either "Completed", "Failed", or "Denied") will be made.
It is, however, recommended that when a Pending payment is encountered, a note
be stored in your application that manual intervention is probably required.
According to PayPal documentation, there are a few cases where this will
happen, which can be obtained from the "pending_reason" CGI input variable.
The possible values and what each means follows (this comes straight from the
PayPal documentation).
=over 4
=item "echeck"
The payment is pending because it was made by an eCheck, which has not yet
cleared.
=item "multi_currency"
You do not have a balance in the currency sent, and you do not have your
Payment Receiving Preferences set to automatically convert and accept this
payment. You must manually accept or deny this payment.
=item "intl"
The payment is pending because you, the merchant, hold an international account
and do not have a withdrawal mechanism. You must manually accept or deny this
payment from your Account Overview.
=item "verify"
The payment is pending because you, the merchant, are not yet verified. You
must verify your account before you can accept this payment.
=item "address"
The payment is pending because your customer did not include a confirmed
shipping address and you, the merchant, have your Payment Receiving Preferences
set such that you want to manually accept or deny each of these payments. To
change your preference, go to the "Preferences" section of your "Profile."
=item "upgrade"
The payment is pending because it was made via credit card and you, the
merchant, must upgrade your account to Business or Premier status in order to
receive the funds.
=item "unilateral"
The payment is pending because it was made to an email address that is not yet
registered or confirmed.
=item "other"
The payment is pending for an "other" reason. For more information, contact
customer service.
=back
=head2 on_failed
Takes a code reference to call in the event of a failed payment notification.
A failed payment "will only happen if the payment was made from your customer's
bank account."
You should record a failed payment in your application.
=head2 on_denied
This code reference is called when a "Denied" payment notification is received.
"This will only happen if the payment was previously pending due to one of the
'pending reasons'" above.
You should record a failed or denied payment in your application.
=head2 on_invalid
This code reference will be called when an invalid request is made. This
usually means that the request B<did not> come from PayPal. According to
PayPal, "if you receive an 'INVALID' notification, it should be treated as
suspicious and investigated." Thus it is strongly recommended that a record of
the invalid request be made.
=head2 duplicate
This code reference is required to prevent duplicate payments. It is called
for potentially successful requests to ensure that it is not a duplicate
postback. It is passed the "txn_id" CGI parameter, which is the
PayPal-generated transaction ID. You should check this parameter against your
order database. If you have already recorded this payment as successfully
made, should should return C<undef> from this function, to indicate that the
duplicate check failed. If the transaction ID is okay (i.e. is not a
duplicate) return 1 to continue.
=head2 recurring
A successful recurring payment has been made. You should set a "paid" status
for the item in question.
=head2 recurring_signup
=head2 recurring_cancel
=head2 recurring_failed
=head2 recurring_eot
=head2 recurring_modify
These are called when various things have happened to the subscription. In
particular, signup refers to a new subscription, cancel refers to a cancelled
subscription, failed refers to a failed payment, eot refers to a subscription
that ended naturally (i.e. an end was set when the subscription was initially
made), and modify is called when a payment has been modified.
=head2 email
This code reference, like duplicate, is called to ensure that the payment was
sent to the correct account. An e-mail address is passed in which must be the
same as the primary account's e-mail address. If it is the same, return C<1>.
If it is I<not> the same, you should return C<undef> and store a note asking
the user to check that the PayPal e-mail address they have provided is the
correct, primary, PayPal e-mail address.
=head2 on_error
This code reference is optional, but recommended. It is called when a
non-PayPal generated error occurs - such as a failure to connect to PayPal. It
is recommended that you provide this code reference and log any errors that
occur. The error message is passed in.
=head1 INSTRUCTIONS
To implement PayPal payment processing, there are a number of steps required in
addition to this module. Basically, this module handles only the postback
stage of the PayPal IPN process.
Full PayPal single item, subscription, and IPN documentation is available at
the URL's listed in the L<SEE ALSO|/"SEE ALSO"> section.
=head2 Directing customers to PayPal
This is done by creating a web form containing the following variables. Your
form, first of all, must post to C<https://www.paypal.com/cgi-bin/webscr>.
Your form should contains various PayPal parameters, as outlined in the PayPal
manuals linked to in the L<SEE ALSO|/"SEE ALSO"> section.
Of particular note is the "notify_url" option, which should be used to specify
a postback URL for PayPal IPN postbacks.
The below is simply a list of the required fields, and only those fields that
are absolutely required are described. For descriptions of each field, check
the PayPal Single Item Purchases Manual.
=over 4
=item cmd
Must be set to "_xclick".
=item business
Your PayPal ID (e-mail address). Must be confirmed and linked to your Verified
Business or Premier account.
=item item_name
=item item_number
=item image_url
=item no_shipping
=item return
Although optional, this is highly recommend - takes a URL to bring the buyer
back to after purchasing. If not specified, they'll remain at PayPal.
=item rm
Return method for the L<return|/return> option. If "1", a GET request without
the transaction variables will be made, if "2" a POST request WITH the transaction
variables will be made.
=item cancel_return
=item no_note
=item cn
=item cs
=item on0
=item on1
=item os0
=item os1
=item quantity
The quantity of items being purchased. If omitted, defaults to 1 and will not
be shown in the payment flow.
=item undefined_quantity
"If set to "1", the user will be able to edit the quantity. This means your
customer will see a field next to quantity which they must complete. This is
optional; if omitted or set to "0", the quantity will not be editable by the
user. Instead, it will default to 1"
=item shipping
=back
=head2 IPN
Before PayPal payment notification can occur, you must instruct the user to
enable Instant Payment Notification (IPN) on their PayPal account. The
postback URL should be provided and handled by you either by detecting a PayPal
request in your main .cgi script (recommended), or through the use of an
additional .cgi script exclusively for PayPal IPN.
If adding to your existing script, it is recommended to look for the 'txn_type'
CGI parameter, which will be set for PayPal IPN postbacks.
Once IPN has been set up, you have to set up your application to direct users
to PayPal in order to initiate a PayPal payment.
=head1 SEE ALSO
L<https://www.paypal.com/html/single_item.pdf> - Single Item Purchases Manual
L<https://www.paypal.com/html/subscriptions.pdf> - Subscriptions and Recurring
Payments Manual
L<https://www.paypal.com/html/ipn.pdf> - IPN Manual
=head1 MAINTAINER
Jason Rhinelander
=head1 COPYRIGHT
Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
http://www.gossamer-threads.com/
=head1 VERSION
Revision: $Id: PayPal.pm,v 1.8 2006/04/08 03:42:05 brewt Exp $
=cut

View File

@ -0,0 +1,466 @@
# ====================================================================
# 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