Apache-Centipaid

 view release on metacpan or  search on metacpan

RCS/Centipaid.pm,v  view on Meta::CPAN



1.3
date	2003.01.25.18.20.54;	author root;	state Exp;
branches;
next	1.2;

1.2
date	2002.11.16.18.40.11;	author root;	state Exp;
branches;
next	1.1;

1.1
date	2002.11.15.20.06.45;	author root;	state Exp;
branches;
next	;


desc
@Initial version
@


1.3
log
@fixed bug that allowed user access with an partial receipt number
@
text
@# Apache::Centipaid
# Version $Revision: 1.2 $
#
# $Id: Centipaid.pm,v 1.2 2002/11/16 18:40:11 adonis Exp adonis $
#
# Written by Adonis El Fakih (adonis@@aynacorp.com) Copyright 2002
# 
# This perl module allows site administrators to integrate the
# centipaid.com payment system without making changes to their 
# existing website.
#
# centipaid offers a micropayment solution that allows users
# to pay for online access using an internet stamp, inctead of
# paying using a credit card number, which requieres the user 
# to divulge their credit card number to the site operator.
# for more information on the micro-payment system please visit
# http://www.centipaid.com/
# 
#
# This module may be distributed under the GPL v2 or later.
#
#


package Apache::Centipaid;

use Apache ();
use Apache::Constants qw(OK REDIRECT AUTH_REQUIRED DECLINED FORBIDDEN DECLINED SERVER_ERROR);
use Apache::File;
use IO::Socket;
use Net::hostent;
use DBI;
use CGI::Cookie;



use strict;

$Apache::Centipaid::VERSION = '1.2';

sub need_to_pay($) {
	my $r = shift(@@_);
	my $payto = $r->dir_config("acct") || 0;
	my $amount = $r->dir_config("amount") || 0;
	my $duration = $r->dir_config("duration") || 0;
	my $uri =  $r->uri;
	my $access =  $r->dir_config("access") || 0;
	my $domain =  $r->dir_config("domain") || 0;
	my $lang =  $r->dir_config("lang") || "en";
	my $https =  $r->dir_config("https") || "https://pay.centipaid.com";

	$r->content_type('text/html');
	$r->header_out(Location =>"$https/?payto=$payto&amount=$amount&duration=$duration&access=$access&domain=$domain&path=$uri&lang=$lang");
	return REDIRECT;

}


sub to_seconds ($) {
    my $duration = shift(@@_);
    my $multi;
    my $seconds;

    if ( $duration =~ /^(\d+)(.+)/ ) {
    	my $num = $1;
	my $ltr = $2;
		if ( lc($ltr) eq "m" ) { $multi = 30*24*60*60;}
		if ( lc($ltr) eq "w" ) { $multi = 7*24*60*60;}
		if ( lc($ltr) eq "d" ) { $multi = 24*60*60;}
		if ( lc($ltr) eq "h" ) { $multi = 60*60;}
		$seconds = $multi * $num;
	}

    return $seconds;

}

sub handler {
   
my ($r) = shift;
my $debug = $r->dir_config("debug") || 0;
my $payto = $r->dir_config("acct") || 0;
my $server = $r->dir_config("authserver") || 0;
my $port = $r->dir_config("authport") || 0;
my $pass = $r->dir_config("pass") || 0;
my $amount = $r->dir_config("amount") || 0;
my $access = $r->dir_config("access") || 0;
my $domain = $r->dir_config("domain") || 0;
my $duration = to_seconds($r->dir_config("duration") || 0);
my $enforce_ip = $r->dir_config("enforce_ip") || 0;


# database variables
my $dbtype = $r->dir_config("dbtype") || "mysql";
my $dbhost = $r->dir_config("dbhost") || "localhost";
my $dbport = $r->dir_config("dbport") || "3306";
my $dbname = $r->dir_config("dbname") || 0;
my $dbuser = $r->dir_config("dbuser") || 0;
my $dbpass = $r->dir_config("dbpass") || 0;

#request info
my $c = $r->connection; 
my $ip = $c->remote_ip;
my %args = $r->args;
my $uri = $r->uri;
my $cookie_name = $r->auth_name . "_". $r->auth_type;
my $prefix = "$$ Apache::Centipaid"; 


# connect to the database
my $dsn = "DBI:$dbtype:database=$dbname;host=$dbhost;port=$dbport";
my $dbh = DBI->connect($dsn,$dbuser,$dbpass);

my $connect_to ="$server:$port";
my $line;
my $and;


#cookie information
my %cookies = CGI::Cookie->parse($r->header_in('Cookie'));
my $cookie;
if ( $cookies{$cookie_name} =~ /$cookie_name=([^;]+)/ ) { $cookie = $1; }

	

#print some stats if debug is on
$r->log_error("$prefix: payto $payto") if $debug >= 2;
$r->log_error("$prefix: server $server") if $debug >= 2;
$r->log_error("$prefix: port $port") if $debug >= 2;
$r->log_error("$prefix: ip $ip") if $debug >= 2;
$r->log_error("$prefix: key rcpt = $args{centipaid_rcpt}") if $debug >= 2;
$r->log_error("$prefix: cookie = $cookie") if $debug >= 2;
$r->log_error("$prefix: uri = ". $r->uri) if $debug >= 2;





# else we need to check if there us any indication that the user has paid
# by checking the variables	
if ( $args{centipaid_rcpt} ) {
	my $rcpt  = $args{centipaid_rcpt};	
	
	$r->log_error("$prefix: Autheticating receipt $rcpt via socket://$payto:$pass\@@$connect_to/") if $debug >= 2;
	
	my $auth_server = IO::Socket::INET->new("$connect_to");
   	my $crlf = "\015\012";
   	unless ( $auth_server ) { 
		$r->log_error("Could not connect to $connect_to") if $debug >= 2;
		return;
	}
   
	$auth_server->autoflush(1);
   	print $auth_server "PAYTO:$payto"."$crlf";
   	print $auth_server "PASS:$pass"."$crlf";
   	print $auth_server "RCPT:$rcpt"."$crlf";

	# format the amount in a way that we make sure it becomes a float
	$amount = sprintf("%.6f",$amount);
	while (<$auth_server>) {
		chomp;
		my $received = $_;

		
		$r->log_error("CLIENT:$received") if $debug >= 2;
		
		if ($received =~ /^250 OK PAID(.+)/) {
			# format the paid in a way that we make sure it becomes a float
			my $paid = sprintf("%.6f",$1);
			$r->log_error("Paid [$paid] Amount [$amount]") if $debug >= 5;
			
			# if the amount paid is greater or equal to what 
			# is requiered then it is ok
			if ( $paid >= $amount ) { 
				$r->log_error("$paid  == $amount ") if $debug >= 5;
				
				# insert value in DB
				my $sql =    qq{
						insert into rcpt (rcpt,date,expire,paid,ip,zone) 
						values (
							"$rcpt",NOW(),
							DATE_ADD(NOW(),INTERVAL $duration SECOND),
							$paid,
							"$ip",
							"$cookie_name"
							)
						};
				my $sth =  $dbh->do("$sql");

				# set the cookie so we do not call the server again..
				my $str = CGI::Cookie->new(-name => "$cookie_name",
							   -path=> "$access",
							   -domain => "$domain",
							   -value => "$rcpt",
							   -expires => "+".$duration."s");

				$r->err_headers_out->add('Set-Cookie' => $str);	
				$r->content_type('text/html');
				$r->header_out(Location =>"$uri");
				return REDIRECT;
			}# if paid == amount
			
		}#end if 250
		
		
		if ($received =~ /^500/) {
			# if we get a 500 code then it was an invalid receipt
			$r->log_error("Invalid transaction for receipt $rcpt") if $debug >= 2;

			#send them back to pay
			$r->content_type('text/html');
			$r->header_out(Location =>"$uri");
			return REDIRECT;
			#need_to_pay($r);
		}#end if 500
		
		
   	}# end while	

} elsif ( $cookie ) {

#if there is a cookie, and the cookie is what we are looking for, then
# then this user has been here, and we will check it against valid ones in
# the database..
	my @@row;
	
	$r->log_error("$prefix: Autheticating receipt $cookie via db://$dbuser:$dbpass\@@$dbhost:$dbport/$dbname/") if $debug >= 2;


	# if the site manager wants to tie the receipt to one ip, he/she can do that to insure that one recipt is used
	# per ip.  THis is not recommended with site that receive a lot of users using proxies, since the ip's may be shared
	# or in cases the user is using a dialup
	if ( $enforce_ip == 1) {$and = "and ip = '$ip'";} 
	
	# select records matching rcpt and within expiry date..
	my $sql = qq{select rcpt,date,expire,paid,ip from rcpt where rcpt="$cookie" and expire >= NOW() $and};

	my $sth = $dbh->prepare($sql);
	$sth->execute or db_err("Unable to execute query \n$sql\n", $dbh->errstr);
	while ( @@row = $sth->fetchrow_array ) {	
		my ($rcpt,$date,$expire,$paid,$ip) = @@row;
		$r->log_error("$prefix: receipt record found $rcpt,$date,$expire,$paid,$ip") if $debug >= 2;
		return OK;
	}		
	
	
	#if we are at this stage, then the cookie is not valid or not found and we should remove it.. 
	my $str = CGI::Cookie->new(-name => "$cookie_name",
					   -path=> "$access",
					   -domain => "$domain",
					   -value => "",
					   -expires => "-".$duration."s");

	$r->err_headers_out->add('Set-Cookie' => $str);	
	need_to_pay($r);
	
		
	
} else {

# if there is no receipt, and no cookie, then they need to pay
need_to_pay($r);

}#end check for cookie

	
} 

1;

__END__

=head1 NAME

$Revision: 1.2 $

B<Apache::Centipaid> - mod_perl AuthenHandler 


=head1 SYNOPSIS


 #in httpd.com  
 <directory /document_root/path_path>
 AuthName centipaid
 AuthType custom
 PerlAuthenHandler Apache::Centipaid
 require valid-user 

 PerlSetVar acct account_name
 PerlSetVar pass receipt_password
 PerlSetVar amount 0.01
 PerlSetVar duration 1d
 PerlSetVar access /pay_path
 PerlSetVar domain your.domain.name
 PerlSetVar lang en
 PerlSetVar enforce_ip 0

 PerlSetVar https https://pay.centipaid.com   
 PerlSetVar authserver centipaid_receipt_server
 PerlSetvar authport 2021
    
 PerlSetVar dbtype mysql
 PerlSetVar dbhost localhost
 PerlSetVar dbport 3306
 PerlSetVar dbname centipaid_rcpt
 perlSetVar dbuser user
 perlSetVar dbpass pass
 </directory>


=head1 REQUIRES

Perl5.004_04, mod_perl 1.15, IO::Socket, Net::hostent, DBI, DBD, DBD::mysql, CGI::Cookie;


=head1 DESCRIPTION

B<Apache::Centipaid> is a mod_perl Authentication handler used in 
granting access to users wishing to access paid web services, after 
making payment on centipaid.com which process micropayments using
internet stamps. 

Centipaid.com offers websites the flexibility to charge small amounts
of money, also refered to as B<micropayment>, to users wishing to
access to their web services without the complexity of setting up 
e-commerce enabled site, or to deal with expensive credit card 
processing options. Users benefit from not having to reveal their
identity or credit card information everytime they decide to visit a 
website.  Instead, centipaid allows users to simply pay using
a pre-paid internet stamp, by simply uploading the stamp to centipaid's
site. The stamps are valid in all sites using centipaid.com payment
system.

To access a site, recipts are issued by centipaid and are used to track 
valid payments.  This information is captured and processed by the
Apache::Centipaid module.  The information is then stored locally to 
any SQL database installed.  The module relies on DBD/DBI interface
so as long as the database has a DBD interface installed under Perl,
and utilizes SQL to make queries and inserts, then you should be able 
to maintain and track your receipts.

B<How does it work?>
A user visits a website, or a section with a site, that requieres an
accecss fee.  The webserver will intercept the request, and realize that
the user has not made a payment, and hence they are directed to 
centipaid.com to pay for access.  Centipaid will inform the user what 
they are paying for in a standard language, along with the fee associated 
with the access, and the duration of the granted access.

If the user agrees to the terms, s/he proceeds to select an electronic
stamp from his computer that has enough funds to pay for the access.
Centipaid will autheticate the stamp, deduct the access fee, and issue the
user a receipt number.  The user will see a receipt on his page that 
re-iterates the terms, amount paid, and internet stamp balance. A link will 
be also available for the user to click on to be transported back to the
page they came from, along with the receipt number.  Once the user makes
a payment, they are shown the amount paid, the balance left on the card,
and a link back to the page they were trying to access.  Once they press
the link, they are forwarded back to the initial page they tried to 
access.

At this stage, Apache::Centipaid realizes that a rececipt is being submited
as payment.  It takes the receipt number and autheticates with centipaid's 
receipt server to insure that the receipt is a valid one, and that the 
proper funds have been paid.

If the rececipt is valid, then the information is stored locally.  The 
information stored included the ip of the client (for optional enforcment 
of payment tied to ip), amount paid, date of transaction, expiry date, 
and the zone the payment covers.  And since centipaid micropayment system 
is designed to allow users to pay without having to provide username, 
password or any other payment information other than the recipt 
number, it makes the process easy to manage and neither the payer or 



( run in 2.048 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )