Business-OnlinePayment-Ogone
view release on metacpan or search on metacpan
lib/Business/OnlinePayment/Ogone.pm view on Meta::CPAN
use MIME::Base64;
# ABSTRACT: Online payment processing via Ogone
our $VERSION = 0.2;
our $API_VERSION = 4.9;
# Ogone config defaults and info ######################################################################################
our %defaults = (
server => 'secure.ogone.com',
port => 443,
);
our %info = (
'info_compat' => '0.01', # always 0.01 for now,
'gateway_name' => 'Ogone',
'gateway_url' => 'http://www.ogone.com/',
'module_version' => $VERSION,
'supported_types' => [ qw( CC ) ],
'token_support' => 0, #card storage/tokenization support
'test_transaction' => 1, #set true if ->test_transaction(1) works
'supported_actions' => [
'Authorization Only',
'Post Authorization',
'Query',
'Credit',
]
);
# Methods #############################################################################################################
sub _info {
return \%info;
}
sub set_defaults {
my $self = shift;
my %data = @_;
$self->{$_} = $defaults{$_} for keys %defaults;
$self->build_subs(qw/http_args result_xml/);
}
sub submit {
my $self = shift;
my %data = $self->content();
# do not allow submitting same object twice
croak 'submitting same object twice is not allowed' if $self->{_dirty}; $self->{_dirty} = 1;
# Turn the data into a format usable by the online processor
croak 'no action parameter defined in content' unless exists $self->{_content}->{action};
# Default currency to Euro
$self->{_content}->{currency} ||= 'EUR';
# Table to translate from Business::OnlinePayment::Ogone args to Ogone API args
# The values of this hash are also used as a list of allowed args for the HTTP POST request, thus preventing information leakage
my %ogone_api_args = (
# credentials
login => 'USERID',
password => 'PSWD',
PSPID => 'PSPID',
# primary identifier
invoice_number => 'orderID',
# transaction identifiers (action = query)
payid => 'PAYID',
payidsub => 'PAYIDSUB',
# credit card data
card_number => 'CARDNO',
cvc => 'CVC',
expiration => 'ED',
alias => 'ALIAS',
# financial data
currency => 'Currency',
amount => 'amount',
# Ogone specific arguments
operation => 'Operation', # REN, DEL, DES, SAL, SAS, RFD, RFS
eci => 'ECI', # defaults 7: e-commerce with ssl (9: recurring e-commerce)
accepturl => 'accepturl',
declineurl => 'declineurl',
exceptionurl => 'exceptionurl',
paramplus => 'paramplus',
complus => 'complus',
language => 'LANGUAGE',
# Business::OnlinePayment common
description => 'COM',
name => 'CN',
email => 'EMAIL',
address => 'Owneraddress',
zip => 'OwnerZip',
city => 'ownertown',
country => 'ownercty',
phone => 'ownertelno',
# client authentication (not used directly, only here as valid HTTP POST arg)
SHASign => 'SHASign', # see sha_key, sha_type
# 3d secure arguments
flag3d => 'FLAG3D',
win3ds => 'win3ds',
http_accept => 'HTTP_ACCEPT',
http_user_agent => 'HTTP_USER_AGENT',
# recurrent fields
subscription_id => 'SUBSCRIPTION_ID',
subscription_orderid => 'SUB_ORDERID',
subscription_status => 'SUBSCRIPTION_STATUS',
startdate => 'SUB_STARTDATE',
enddate => 'SUB_ENDDATE',
status => 'SUB_STATUS',
period_unit => 'SUB_PERIOD_UNIT', # 'd', 'ww', 'm' (yes two 'w's) for resp daily weekly monthly
period_moment => 'SUB_PERIOD_MOMENT', # Integer, the moment in time on which the payment is (0-7 when period_unit is ww, 1-31 for d, 1-12 for m?)
period_number => 'SUB_PERIOD_NUMBER',
lib/Business/OnlinePayment/Ogone.pm view on Meta::CPAN
$http_req_args{SUB_AMOUNT} = $http_req_args{amount};
$http_req_args{SUB_ORDERID} = $http_req_args{orderID};
}
# PSPID might be entered in lowercase (as per old documentation)
$http_req_args{PSPID} = $self->{_content}{pspid} if defined $self->{_content}{pspid};
# Calculate sha1 by default, but has to be enabled in the Ogone backend to have any effect
my ($sha_type) = ($self->{_content}->{sha_type} =~ m/^(1|256|512)$/);
# Create a reference to the correct sha${sha_type}_hex function, default to SHA-1
my $sha_hex = sub { my $type = shift; no strict; &{"sha".($type || 1)."_hex"}(@_); use strict; };
# Algo: make a list of "KEY=value$passphrase" sort alphabetically
my $signature = join('',
sort map { uc($_) . "=" . $http_req_args{$_} . ($self->{_content}{sha_key} || '') }
keys %http_req_args);
$http_req_args{SHASign} = $sha_hex->($sha_type,$signature);
# Construct the URL to query, taking into account the action and test_transaction values
my %action_file = (
qr/normal authorization/i => 'orderdirect.asp',
qr/authorization only/i => 'orderdirect.asp',
qr/recurrent authorization/i => 'orderdirect.asp',
qr/post authorization/i => 'maintenancedirect.asp',
qr/query/i => 'querydirect.asp',
);
my $uri_dir = $self->test_transaction() ? 'test' : 'prod';
my ($uri_file) = map { $action_file{$_} }
grep { $self->{_content}->{action} =~ $_ }
keys %action_file;
croak 'unable to determine URI path, is the action parameter one of ( authorization only | normal authorization | post authorization | query | recueent authorization)' unless $uri_file;
# Construct the path to be used in https_post
$self->{path} = '/ncol/'.$uri_dir.'/'.$uri_file;
# Save the http args for later inspection
$self->http_args(\%http_req_args);
# Submit the transaction to the processor and collect a response.
my ($page, $response_code, %reply_headers) = $self->https_post(%http_req_args);
# Call server_response() with a copy of the entire unprocessed response
$self->server_response([$response_code, \%reply_headers, $page]);
my $xml = XMLin($page, ForceArray => [], KeyAttr => [] );
# Store the result xml for later inspection
$self->result_xml($xml);
croak 'Ogone refused SHA digest' if $xml->{NCERRORPLUS} =~ m#^unknown order/1/s#;
# Call is_success() with either a true or false value, indicating if the transaction was successful or not.
if ( $response_code =~ m/^200/ ) {
$self->is_success(0); # defaults to fail
# croak 'incorrect credentials. WARNING: continuing with bad credentials will block your account s: '.$xml->{STATUS}.'{}'.$xml->{NCERROR} if $xml->{NCERROR} eq '50001119';
if ( $xml->{STATUS} == 46 ) { $self->is_success(1) } # identification required
if ( $xml->{STATUS} == 5 ) { $self->is_success(1) } # authorization accepted
if ( $xml->{STATUS} == 9 ) { $self->is_success(1) } # payment accepted
if ( $xml->{STATUS} == 91 ) { $self->is_success(1) } # partial payment accepted
if ( $xml->{STATUS} == 61 ) { $self->is_success(1) } # Author. deletion waiting
if ( $xml->{STATUS} == 2 ) { $self->failure_status('refused') } # authorization refused
if ( $xml->{STATUS} == 0 && $xml->{NCERROR} eq '50001134' ) { $self->failure_status('declined') } # 3d secure wrong identification
if ( $xml->{STATUS} == 0 && $xml->{NCERRORPLUS} =~ m/status \(91\)/ ) {
$self->failure_status('declined');
$self->error_message('Operation only allowed on fully completed transactions (status may not be 91)'); }
} else {
warn "remote server did not respond with HTTP 200 status code";
$self->is_success(0)
}
# Extract the base64 encoded HTML part
if ( $xml->{STATUS} == 46 ) {
my $html = decode_base64($xml->{HTML_ANSWER});
# remove sillyness
$html =~ s/
//g;
$html =~ s///g;
# TODO: parse
#open my $fh, '>', '/tmp/ogone_'.$self->{_content}->{win3ds}.'.html';
#print $fh $html;
}
# Call result_code() with the servers result code
$self->result_code($xml->{NCERROR});
# If the transaction was successful, call authorization() with the authorization code the processor provided.
if ( $self->is_success() ) {
$self->authorization($xml->{PAYID});
}
# If the transaction was not successful, call error_message() with either the processor provided error message, or some error message to indicate why it failed.
if ( not $self->is_success() and $xml->{NCERRORPLUS} ne '!' ) { # '!' == no errorplus
$self->error_message($xml->{NCERRORPLUS});
}
}
42;
__END__
=head1 NAME
Business::OnlinePayment::Ogone - Online payment processing via Ogone
=head1 SYNOPSYS
use common::sense;
use Data::Dumper;
use Business::OnlinePayment;
my $tx = new Business::OnlinePayment('Ogone', pspid => 'fred',
login => 'bedrock_api',
password => 'fl1nst0ne' );
lib/Business/OnlinePayment/Ogone.pm view on Meta::CPAN
=head2 Features
=over 4
=item * code is documented and hopefully quite readable
=item * prevents submitting your transaction twice
=item * accepts EUR and US currencies (defaults to Euro)
=item * allows creation of VISA Alias mitigating the need to store them temporary (RES -> SAL)
=item * allows creation of Recurrent VISA bills (Subscriptions)
=item * close coupling to native API, easy to crossreference
=item * allows Ogone's native operations: RES, REN, DEL, DES, SAL, SAS, RFD, RFS
=item * uses SHASign to protect your request's integrity
=item * implements SHA-1, SHA-256 and SHA-512 signing
=item * allows switching between test and production environments
=item * low-fat is_success() check, Ogone::Status has more details
=item * no dependency on Ogone::Status if memory is a constraint
=back
=head1 PROGRAM FLOW
You have a choice when implementing credit card processing. You can pocess the money transfer in one or more steps.
If you choose to go with one step, customers will be billed directly.
=over 4
=item B<action> => 'Normal Authorization' (with B<operation> => 'SAL' (default))
=item B<action> => 'Authorization Only' (with B<operation> => 'RES' (default)) then B<action> => 'Post Authorization' (with B<operation> => 'SAS' (default))
=back
=head1 METHODS
=head2 new()
Not used directly. Creates a new instance. It's possible to pass credential infomation in the constructor.
my $tx = new Business::OnlinePayment('Ogone', pspid => 'fred', login => 'bedrock_api', password => 'fl1nst0ne');
=head2 content()
This method takes a lot of parameters to prepare the transaction to be submitted. Depending on these parameters the
payment processor will act on them in different ways, you can consider it a sort of dispatch table. The main actors are
C<action>, C<alias>, C<win3ds>.
=head3 content() internal parameter mappings
# credentials
login => 'USERID',
password => 'PSWD',
PSPID => 'PSPID',
# primary identifier
invoice_number => 'orderID',
# transaction identifiers (action = query)
payid => 'PAYID',
payidsub => 'PAYIDSUB',
# credit card data
card_number => 'CARDNO',
cvc => 'CVC',
expiration => 'ED',
alias => 'ALIAS',
# financial data
currency => 'Currency',
amount => 'amount',
# Ogone specific arguments
operation => 'Operation', # REN, DEL, DES, SAL, SAS, RFD, RFS
eci => 'ECI', # defaults 7: e-commerce with ssl (9: recurring e-commerce)
accepturl => 'accepturl',
declineurl => 'declineurl',
exceptionurl => 'exceptionurl',
paramplus => 'paramplus',
complus => 'complus',
language => 'LANGUAGE',
# Business::OnlinePayment common
description => 'COM',
name => 'CN',
email => 'EMAIL',
address => 'Owneraddress',
zip => 'OwnerZip',
city => 'ownertown',
country => 'ownercty',
phone => 'ownertelno',
# client authentication (not used directly, only here as valid HTTP POST arg)
SHASign => 'SHASign', # see sha_key, sha_type
# 3d secure arguments
flag3d => 'FLAG3D',
win3ds => 'win3ds',
http_accept => 'HTTP_ACCEPT',
http_user_agent => 'HTTP_USER_AGENT',
# recurrent fields
subscription_id => 'SUBSCRIPTION_ID',
subscription_orderid => 'SUB_ORDERID',
subscription_status => 'SUBSCRIPTION_STATUS',
startdate => 'SUB_STARTDATE',
enddate => 'SUB_ENDDATE',
status => 'SUB_STATUS',
period_unit => 'SUB_PERIOD_UNIT', # 'd', 'ww', 'm' (yes two 'w's) for resp daily weekly monthly
period_moment => 'SUB_PERIOD_MOMENT', # Integer, the moment in time on which the payment is (0-7 when period_unit is ww, 1-31 for d, 1-12 for m?)
period_number => 'SUB_PERIOD_NUMBER',
lib/Business/OnlinePayment/Ogone.pm view on Meta::CPAN
=item B<1> some time later, you wish to receive/transfer the money to you. You issue a C<post authorize> request (defaults to C<SAL>)
=item B<2> STATUS=91 indicates the payment is being processed. PAYID and PAYIDSUB are references that identify the current operation on the transaction.
=item B<3> Ogone handles processing with your bank
=item B<4> money has been put into your account. STATUS is set to 9
=back
=head2 Refund
Client Ogone HTTPS Bank
------------------------------------------------------------------------
1 .->+---|Query| orderID or PAYID,PAYIDSUB= -->.
| |
| .<----------------------------------------'
| |
2 STATUS == 9
|
3 `---|Refund| orderID or PAYID,PAYIDSUB= ->. [RFD]
|
4 *<-- STATUS=81 ---------------------------+ (processing)
|
5 `----------->.
| (processed) -$
6 STATUS=8 .<-----------'
=over 4
=item B<1> We want to refund a transaction. To check the transaction is refundable, we must first query it.
=item B<2> Refunds are only possible once a transaction is completed (STATUS = 9) (e.g. not while it is processing = 91), thus loop until so.
=item B<3> Request refund using orderID or PAYID and PAYISUB to identify refundable operation.
=item B<4> STATUS=81 indicates the refund is being processed
=item B<5> Ogone handles processing with your bank
=item B<6> Money has been taken from your account. STATUS is set to 8
=back
=head1 TODO
=over 4
=item * Parse 3d-secure HTML
=item * use SHA1 passwd hashing see: L<https://secure.ogone.com/ncol/test/hash_pswd.aspi>
=back
=head1 TESTING
To test this module you will need to set your credentials in the environment. Put the following in a file in your hoe directory e.g. F<~/.ogone>
The password is not the same as the PSPID password, you will need to enter the API users' password.
export OGONE_PSPID=bob
export OGONE_USERID=bob_api
export OGONE_PSWD=foobar
Limit access to the F<~/.ogone> file
chmod 600 ~/.ogone
Then load this file into your env en perform the testcases:
source ~/.ogone
perl -I lib/ t/*.t
=head1 INTERACTIVE SESSION
[fred@triceratops ~/business-online-payment-ogone] $ curl -L http://cpanmin.us | perl - --self-upgrade
[fred@triceratops ~/business-online-payment-ogone] $ cpanm -S Devel::REPL
[fred@triceratops ~/business-online-payment-ogone] $ re.pl
001:0> use lib './lib';
002:0> use Business::OnlinePayment::Ogone;
003:0> my $tx = new Business::OnlinePayment('Ogone', pspid => 'fred', login => 'bedrock_api', password => 'fl1nst0ne');
bless( {
login => "bedrock_api",
passwprd => "fl1nst0ne",
port => 443,
processor => "Ogone",
pspid => "fred",
server => "secure.ogone.com"
}, 'Business::OnlinePayment::Ogone' )
004:0>
=head1 REMOTE SIDE DOCUMENTATION
=head2 Backend
=over 4
=item Ogone Test Backend L<https://secure.ogone.com/ncol/test/frame_ogone.asp>
=item Ogone Prod Backend L<https://secure.ogone.com/ncol/prod/frame_ogone.asp>
=back
=head2 Online Documentation
=over 4
=item Homepage L<http://www.ogone.com>
=item Ogone DirectLink integration guide L<https://secure.ogone.com/ncol/Ogone_DirectLink_EN.pdf>
=item Ogone Alias Manager Option integration guide L<https://secure.ogone.com/ncol/Ogone_Alias_EN.pdf>
( run in 2.022 seconds using v1.01-cache-2.11-cpan-364913b4093 )