--- /dev/null
+blib/
+*.sw?
+Makefile
+Makefile.old
+MYMETA.yml
+pm_to_blib
--- /dev/null
+Revision history for Business-OnlineThirdPartyPayment-FCMB
+
+0.01 unreleased
+ Initial version
--- /dev/null
+package Business::OnlineThirdPartyPayment::FCMB;
+
+use strict;
+use base 'Business::OnlineThirdPartyPayment';
+
+use strict;
+use LWP;
+use URI;
+use Data::Dumper;
+use Date::Format 'time2str';
+use XML::LibXML;
+
+our $VERSION = '0.01';
+our $ENDPOINT_SANDBOX = 'localhost';
+our $ENDPOINT_LIVE = 'fcmbwebpay.firstcitygrouponline.com';
+
+our $DEBUG = 3;
+
+# ISO 4217 currency codes (the relevant ones)
+our %ALPHA_TO_NUM = (
+ USD => 840,
+ UKP => 826,
+ NGN => 566,
+);
+
+sub set_defaults {
+ my $self = shift;
+ my %args = @_;
+ $self->build_subs(qw(username password host));
+ if ( $args{debug} ) {
+ $DEBUG = $args{debug};
+ }
+}
+
+sub create {
+ my $self = shift;
+ my %content = @_;
+
+ my %params;
+ $params{'orderId'} = time2str('%Y%m%d%H%M%S', time) . '-' .
+ sprintf('%06d', int(rand(1000000)));
+ $params{'mercId'} = $self->username
+ or die "FCMB merchant ID (username) must be configured\n";
+ $params{'currCode'} = $ALPHA_TO_NUM{$content{currency}};
+ $params{'prod'} = $content{'description'};
+ $params{'email'} = $content{'email'};
+ $params{'amt'} = $content{'amount'};
+
+ my $host = $self->host;
+ if ( $self->test_transaction ) {
+ $host ||= $ENDPOINT_SANDBOX;
+ } else {
+ $host ||= $ENDPOINT_LIVE;
+ }
+ my $url = 'https://' . $host .
+ '/customerportal/MerchantServices/MakePayment.aspx';
+
+ warn Dumper \%params if $DEBUG > 2;
+
+ # we don't post to the url ourselves, just give it to the user
+ $self->is_success(1);
+ $self->redirect($url);
+ $self->post_params(\%params);
+
+ $self->token( $params{'orderId'} );
+}
+
+sub execute {
+ my $self = shift;
+ my %params = @_;
+
+# URL looks like
+# http://merchantA.com/Success?OrderID=988676&TransactionReference=8765678998989779
+ warn Dumper(\%params) if $DEBUG > 2;
+
+ if ($params{'OrderID'}) {
+ $self->token($params{'OrderID'});
+ } else {
+ die 'No order ID returned from processor';
+ }
+ if ($params{'TransactionReference'}) {
+ $self->order_number($params{'TransactionReference'});
+ } else {
+ die 'No transaction reference returned from processor';
+ }
+
+ my $host = $self->host;
+ if ( $self->test_transaction ) {
+ $host ||= $ENDPOINT_SANDBOX;
+ $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
+ } else {
+ $host ||= $ENDPOINT_LIVE;
+ }
+ my $url = 'https://' . $host .
+ '/customerportal/MerchantServices/UpayTransactionStatus.ashx';
+ my $ua = LWP::UserAgent->new;
+ warn "Querying transaction status at $url\n" if $DEBUG;
+ my $response = $ua->get($url,
+ 'MERCHANT_ID' => $self->username,
+ 'ORDER_ID' => $self->token
+ );
+
+ if ( $response->is_success ) {
+
+ local $@;
+ my $parser = XML::LibXML->new;
+ my $doc = eval { $parser->parse_string($response->content) };
+ if ( $@ ) {
+ die "malformed response to transaction status request: $@\n".
+ ($DEBUG ? ("\n\n".$response->content) : '');
+ }
+ my $root = $doc->documentElement;
+ my %hash = map { $_->nodeName => $_->textContent }
+ $root->nonBlankChildNodes;
+
+ warn Dumper \%hash if $DEBUG > 2;
+ if ( $hash{StatusCode} == 0 ) {
+ $self->is_success(1);
+ $self->authorization($hash{PaymentRef});
+ } else {
+ $self->is_success(0);
+ $self->error_message($hash{Status} . ' - ' . $hash{ResponseDescription});
+ }
+ } else {
+ die "No confirmation received: ".$response->status_line;
+ }
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Business::OnlineThirdPartyPayment::FCMB
+
+=head1 DESCRIPTION
+
+Business::OnlineThirdPartyPayment interface to the First City Monument Bank
+(Nigeria) web-based payment processing system.
+
+=head1 NOTES
+
+=over 4
+
+=item FCMB requires the callback URL to be configured statically;
+I<return_url> and I<cancel_url> parameters will be ignored.
+
+=item Set I<username> to your merchant ID (5 digits). No password is needed.
+
+=item The only required transaction fields are currency (NGN, USD, or UKP),
+amount, description, and email.
+
+=item The 'faker' directory contains a simple mock-up of the FCMB interface
+for testing. This was created from the documentation, without reference
+to the real FCMB system, and is NOT known to be accurate.
+
+=back
+
+=head1 AUTHOR
+
+Mark Wells <mark@freeside.biz>
+
+=head1 SEE ALSO
+
+perl(1). L<Business::OnlineThirdPartyPayment>.
+
+=cut
+
--- /dev/null
+Makefile.PL
+MANIFEST
+README
+FCMB.pm
+Changes
+faker/UpayTransactionStatus.ashx
+faker/fcmb.conf
+faker/ConfirmPayment.cgi
+faker/MakePayment.aspx
--- /dev/null
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ 'NAME' => 'Business::OnlineThirdPartyPayment::FCMB',
+ 'VERSION_FROM' => 'FCMB.pm', # finds $VERSION
+ 'AUTHOR' => 'Mark Wells <mark@freeside.biz>',
+ 'PREREQ_PM' => {
+ 'Business::OnlineThirdPartyPayment' => 0.10,
+ 'XML::Simple' => 2,
+ },
+);
--- /dev/null
+Business-OnlineThirdPartyPayment-FCMB is a
+Business::OnlineThirdPartyPayment module for interfacing with
+the First City Monument Bank WebPay system.
+
+INSTALLATION
+
+To install this module, run the following commands:
+
+ perl Makefile.PL
+ make
+ make test
+ make install
+
+SUPPORT AND DOCUMENTATION
+
+After installing, you can find documentation for this module with the
+perldoc command.
+
+ perldoc Business::OnlineThirdPartyPayment::FCMB
+
+COPYRIGHT AND LICENCE
+
+Copyright (C) 2013 Mark Wells
+Copyright (C) 2013 Freeside Internet Services, Inc.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
--- /dev/null
+#!/usr/bin/perl -T
+
+my $LANDING_URL =
+ "http://localhost:2080/selfservice.cgi?action=finish_thirdparty_payment";
+
+use CGI;
+use URI;
+use Date::Format 'time2str';
+use Cache::FileCache;
+use strict;
+
+my $cache = Cache::FileCache->new(
+ { cache_root => '/tmp', namespace => 'FCMB-Faker' }
+);
+
+my $landing = URI->new($LANDING_URL);
+
+my $cgi = CGI->new;
+my $reference = $cgi->param('reference');
+my $txn = $cache->get($reference);
+
+if ( $cgi->param('submit') eq 'Cancel' ) {
+ $txn->{status} = 3; #canceled
+ $landing->query_form(_cancel => 1);
+} else {
+
+ # some information captured from the customer
+ # (in Real Life this would also be their credit card/bank account number)
+ $txn->{first} = $cgi->param('first');
+ $txn->{last} = $cgi->param('last');
+ # set status = the last digit of cents
+ # (0 = success)
+ my $cents = ($txn->{amt} - int($txn->{amt})) * 100;
+ $txn->{status} = $cents % 10;
+ $txn->{date} = time2str('%Y-%m-%d', time);
+
+ $landing->query_form(
+ $landing->query_form,
+ 'OrderID' => $txn->{orderId},
+ 'TransactionReference' => $reference
+ );
+}
+# update the cache
+$cache->set($reference => $txn);
+print $cgi->redirect($landing);
--- /dev/null
+#!/usr/bin/perl -T
+
+use CGI;
+use Cache::FileCache;
+use strict;
+
+my $cache = Cache::FileCache->new(
+ { cache_root => '/tmp', namespace => 'FCMB-Faker' }
+);
+my $cgi = CGI->new;
+my %transaction = map { $_ => ($cgi->param($_) || '') }
+ qw( mercId currCode amt orderId prod email );
+
+my $reference = sprintf('%06d%04d', $transaction{mercId}, int(rand(10000)));
+$transaction{reference} = $reference;
+$transaction{status} = 2; #pending
+
+$cache->set($reference, \%transaction);
+
+my $content = qq!
+<HTML>
+ <HEAD><TITLE>Not FCMB Web Payment</TITLE></HEAD>
+ <BODY><H3>Confirm your payment</H3>
+ <FORM METHOD="POST" ACTION="ConfirmPayment.cgi">
+ <TABLE CELLSPACING=0 STYLE="border: 1px solid">
+ <TR><TD>Order #</TD><TD>!.$transaction{orderId}.qq!</TD></TR>
+ <TR><TD>Product</TD><TD>!.$transaction{prod}.qq!</TD></TR>
+ <TR><TD>Amount </TD><TD>!.$transaction{amt}.qq!</TD></TR>
+ <TR><TD>First Name</TD><TD><INPUT NAME="first"></TD></TR>
+ <TR><TD>Last Name</TD><TD><INPUT NAME="last"></TD></TR>
+ </TABLE><BR>
+ <INPUT TYPE="hidden" name="reference" value="!.$reference.qq!">
+ <INPUT TYPE="submit" name="submit" value="Pay Now">
+ <INPUT TYPE="submit" name="submit" value="Cancel">
+ </FORM>
+ </BODY>
+</HTML>
+!;
+
+print $cgi->header('text/html',
+ 'Content-Length' => length($content));
+print $content;
+
--- /dev/null
+#!/usr/bin/perl -T
+
+use CGI;
+use Cache::FileCache;
+use strict;
+use XML::LibXML;
+
+my $cache = Cache::FileCache->new(
+ { cache_root => '/tmp', namespace => 'FCMB-Faker' }
+);
+
+my @status = (
+ 'Successful', 'Failed', 'Pending', 'Cancelled', 'Not Processed',
+ 'Invalid Merchant', 'Inactive Merchant', 'Inactive Order ID',
+ 'Duplicate Order ID', 'Invalid Amount'
+);
+
+my $cgi = CGI->new;
+my $oid = $cgi->param('ORDER_ID');
+
+# inefficient, but this is not production code, so who cares?
+my ($txn) = grep { $_->{orderId} eq $oid }
+ map { $cache->get($_) } $cache->get_keys;
+my @out;
+if ($txn) {
+ @out = (
+ MerchantID => $txn->{mercId},
+ OrderID => $txn->{orderId},
+ StatusCode => $txn->{status},
+ Status => $status[$txn->{status}],
+ Amount => sprintf('%.2f', $txn->{amt}),
+ Date => $txn->{date},
+ TransactionRef => $txn->{reference},
+ PaymentRef => sprintf('%06d', rand(1000000)),
+ ResponseCode => sprintf('%02d', rand(100)),
+ ResponseDescription => 'response description',
+ CurrencyCode => $txn->{currCode},
+ );
+} else {
+ @out = ( Status => 'Invalid Order ID', StatusCode => '07' );
+}
+my $doc = XML::LibXML::Document->new;
+my $root = $doc->createElement('UPay');
+$doc->setDocumentElement($root);
+while (@out) {
+ my $name = shift @out;
+ my $value = shift @out;
+ my $node = $doc->createElement($name);
+ $node->appendChild( XML::LibXML::Text->new($value) );
+ $root->appendChild($node);
+}
+
+my $content = $doc->toString;
+print $cgi->header('text/xml');
+print $content;
--- /dev/null
+<IfModule mod_ssl.c>
+<VirtualHost localhost:443>
+ ServerAdmin webmaster@localhost
+
+ DocumentRoot /var/www
+ <Directory />
+ Options FollowSymLinks
+ AllowOverride None
+ </Directory>
+ <Directory /var/www/>
+ Options Indexes FollowSymLinks MultiViews
+ AllowOverride None
+ Order allow,deny
+ allow from all
+ </Directory>
+ <Directory /var/www/customerportal>
+ Options ExecCGI
+ AddHandler cgi-script .cgi .aspx .ashx
+ </Directory>
+
+ ErrorLog ${APACHE_LOG_DIR}/fcmb_error.log
+
+ CustomLog ${APACHE_LOG_DIR}/fcmb_access.log combined
+
+ SSLEngine on
+ SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
+ SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
+
+ BrowserMatch "MSIE [2-6]" \
+ nokeepalive ssl-unclean-shutdown \
+ downgrade-1.0 force-response-1.0
+ BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
+
+</VirtualHost>
+</IfModule>