discourse-legacysite-perl/site/slowtwitch.com/cgi-bin/articles/GT/Payment/Remote/2CheckOut.pm
2024-06-17 21:49:12 +10:00

318 lines
8.9 KiB
Perl

# ====================================================================
# 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