package Business::OnlinePayment::CyberSource::Client;
use 5.010;
use strict;
use warnings;
use Moose;
use Class::Load 0.20 qw(load_class);
use Data::Dump 'dump';
use MooseX::Aliases;
use MooseX::StrictConstructor;
use Try::Tiny;
use Business::CyberSource::Client;
use MooseX::Types::CyberSource qw(AVSResult);
use MooseX::Types::Moose qw(Bool HashRef Int Str);
use MooseX::Types::Common::String qw(NonEmptySimpleStr);
# ABSTRACT: CyberSource Client object for Business::OnlinePayment::CyberSource
our $VERSION = '3.000006'; # VERSION
#### Subroutine Definitions ####
# Sends an authorization request to CyberSource
# Accepts: A hash or reference to a hash of request parameters
# Returns: 1 if the transaction was successful and 0 otherwise
sub authorize {
my ( $self, @args ) = @_;
my $class = 'Business::CyberSource::Request::Authorization';
return $self->_authorize( $class, @args );
}
sub sale {
my ( $self, @args ) = @_;
my $class = 'Business::CyberSource::Request::Sale';
return $self->_authorize( $class, @args );
}
sub _authorize {
my ( $self, $class, @args ) = @_;
my $data = $self->_parse_input( @args );
# Validate input
my $message;
$message = 'No request data specified to authorize'
if scalar keys %$data == 0;
$message = 'purchase_totals data must be specified to authorize as a hashref'
unless $data->{purchase_totals} && ref $data->{purchase_totals} eq 'HASH';
$message = 'No payment medium specified to authorize'
unless $data->{card};
$message = 'No reference code specified to authorize'
unless $data->{reference_code};
Exception::Base->throw( $message ) if $message;
unless ( $self->require_avs() ) {
$data->{business_rules} = { ignore_avs_result => 1 };
}
my $request = try {
load_class( $class )->new( $data );
}
catch {
$message = shift;
$self->set_error_message( "$message" );
return $self->is_success();
};
return $request unless $request;
try {
my $response = $self->run_transaction( $request );
if ( $response->is_success() ) {
$self->is_success( 1 );
$self->authorization( $response->auth_code() )
if $response->does( 'Business::CyberSource::Response::Role::Authorization' );
$self->cvv2_response( $response->cv_code() ) if $response->has_cv_code();
}
else {
$self->set_error_message( $response->reason_text() );
}
$self->avs_code( $response->avs_code() )
if $response->does( 'Business::CyberSource::Response::Role::AVS' )
&& $response->has_avs_code;
$self->_fill_fields( $response );
}
catch {
$message = shift;
$self->set_error_message( "$message" );
};
return $self->is_success();
}
# Sends a capture request to CyberSource
# Accepts: A hash or reference to a hash of request parameters
# Returns: 1 if the transaction was successful and 0 otherwise
sub capture {
my ( $self, @args ) = @_;
my $data = $self->_parse_input( @args );
#Validate input
my $message = '';
$message = 'No reference code supplied to capture'
unless $data->{reference_code};
$message = 'No service data supplied to capture'
unless $data->{service};
$message = 'No purchase totals supplied to capture'
unless $data->{purchase_totals};
Exception::Base->throw( $message ) if $message;
my $request = try {
load_class( 'Business::CyberSource::Request::Capture' )->new( $data );
}
catch {
$message = shift;
$self->set_error_message( "$message" );
return $self->is_success();
};
return $request unless $request;
try {
my $response = $self->run_transaction( $request );
if ( $response->is_success() ) {
$self->is_success ( 1 );
}
else {
$self->set_error_message( $response->reason_text() );
}
$self->_fill_fields( $response );
}
catch {
$message = shift;
$self->set_error_message( "$message" );
};
return $self->is_success();
}
# Sends a credit request to CyberSource
# Accepts: A hash or reference to a hash of request parameters
# Returns: 1 if the transaction was successful and 0 otherwise
sub credit {
my ( $self, @args ) = @_;
my $data = $self->_parse_input( @args );
#Validate input
my $message = '';
$message = 'No reference code supplied to credit'
unless $data->{reference_code};
unless ( $data->{service} && $data->{service}->{request_id} ) {
$message = 'No bill_to supplied to credit'
unless $data->{bill_to};
}
$message = 'No purchase totals supplied to credit'
unless $data->{purchase_totals};
Exception::Base->throw( $message ) if $message;
my $request = try {
load_class( 'Business::CyberSource::Request::Credit' )->new( $data );
}
catch {
$message = shift;
$self->set_error_message( "$message" );
return $self->is_success();
};
return $request unless $request;
try {
my $response = $self->run_transaction( $request );
if ( $response->is_success() ) {
$self->is_success ( 1 );
}
else {
$self->set_error_message( $response->reason_text() );
}
$self->_fill_fields( $response );
}
catch {
$message = shift;
$self->set_error_message( "$message" );
};
return $self->is_success();
}
# Sends a AuthReversal request to CyberSource
# Accepts: a hash or hashref of request parameters
# Returns: 1 if success or 0
sub auth_reversal {
my ( $self, @args ) = @_;
my $data = $self->_parse_input( @args );
#Validate input
my $message;
$message = 'No reference code supplied to void'
unless $data->{reference_code};
$message = 'No service data supplied to void'
unless $data->{service};
$message = 'No purchase totals supplied to void'
unless $data->{purchase_totals};
Exception::Base->throw( $message ) if $message;
my $request = try {
load_class( 'Business::CyberSource::Request::AuthReversal' )->new( $data );
}
catch {
$self->set_error_message( "$_" );
return $self->is_success();
};
try {
my $response = $self->run_transaction( $request );
if ( $response->is_success() ) {
$self->is_success ( 1 );
}
else {
$self->set_error_message( $response->reason_text() );
}
$self->_fill_fields( $response );
}
catch {
$message = shift;
$self->set_error_message( "$message" );
};
return $self->is_success();
}
# Sets various response fields
# Accepts: Nothing
# Returns: Nothing
sub _fill_fields {
my ( $self, $response ) = @_;
my $res = {};
return unless ( $response and $response->isa( 'Business::CyberSource::Response' ) );
if ( $response->trace() ) {
$res = $response->trace->response();
}
else {
Exception::Base->throw( 'Request failed' );
}
my $h = $res->headers();
my $names = [ $h->header_field_names() ];
my $headers = { map { $_ => $h->header( $_ ) } @$names }; ## no critic ( BuiltinFunctions::ProhibitVoidMap )
$self->order_number( $response->request_id() );
$self->response_code( $res->code() );
$self->response_page( $res->content() );
$self->response_headers( $headers );
$self->result_code( $response->reason_code() );
return;
}
# Resets all transaction fields
# Accepts: Nothing
# Returns: Nothing
sub _clear_fields {
my ( $self ) = @_;
my $attributes = [ qw(
success authorization order_number card_token fraud_score fraud_transaction_id
response_code response_headers response_page result_code avs_code
cvv2_response
) ];
$self->$_() foreach ( map { "clear_$_" } @$attributes );
return;
}
# builds the Business::CyberSource client
# Accepts: Nothing
# Returns: A reference to a Business::CyberSource::Client object
sub _build_client { ## no critic ( Subroutines::ProhibitUnusedPrivateSubroutines )
my ( $self ) = @_;
my $username = $self->login();
my $password = $self->password();
my $test = $self->test_transaction();
my $data = {
username => $username,
password => $password,
production => ! $test,
};
my $client = Business::CyberSource::Client->new( $data );
return $client;
}
#### Object Attributes ####
has is_success => (
isa => Bool,
is => 'rw',
default => 0,
required => 0,
clearer => 'clear_success',
init_arg => undef,
lazy => 1,
);
# Authorization code
has authorization => (
isa => Str,
is => 'rw',
required => 0,
predicate => 'has_authorization',
clearer => 'clear_authorization',
init_arg => undef,
lazy => 0,
);
# Number identifying the specific request
has order_number => (
isa => Str,
is => 'rw',
required => 0,
predicate => 'has_order_number',
clearer => 'clear_order_number',
init_arg => undef,
lazy => 0,
);
# Used in stead of card number (not yet supported)
has card_token => (
isa => Str,
is => 'rw',
required => 0,
predicate => 'has_card_token',
clearer => 'clear_card_token',
init_arg => undef,
lazy => 0,
);
# score assigned by ... (not yet supported)
has fraud_score => (
isa => Str,
is => 'rw',
required => 0,
predicate => 'has_fraud_score',
clearer => 'clear_fraud_score',
init_arg => undef,
lazy => 0,
);
# Transaction id assigned by ... (not yet supported)
has fraud_transaction_id => (
isa => Str,
is => 'rw',
required => 0,
predicate => 'has_fraud_transaction_id',
clearer => 'clear_fraud_transaction_id',
init_arg => undef,
lazy => 0,
);
# HTTP response code
has response_code => (
isa => Int,
is => 'rw',
required => 0,
predicate => 'has_response_code',
clearer => 'clear_response_code',
init_arg => undef,
lazy => 0,
);
# HTTP response headers
has response_headers => (
isa => HashRef,
is => 'rw',
required => 0,
predicate => 'has_response_headers',
clearer => 'clear_response_headers',
init_arg => undef,
lazy => 0,
);
# HTTP response content
has response_page => (
isa => Str,
is => 'rw',
required => 0,
predicate => 'has_response_page',
clearer => 'clear_response_page',
init_arg => undef,
lazy => 0,
);
# ...
has result_code => (
isa => Str,
is => 'rw',
required => 0,
predicate => 'has_result_code',
clearer => 'clear_result_code',
init_arg => undef,
lazy => 0,
);
# address verification response code
has avs_code => (
isa => AVSResult,
is => 'rw',
required => 0,
predicate => 'has_avs_code',
clearer => 'clear_avs_code',
init_arg => undef,
lazy => 0,
);
# CVV2 response value
has cvv2_response => (
isa => Str,
is => 'rw',
required => 0,
predicate => 'has_cvv2_response',
clearer => 'clear_cvv2_response',
init_arg => undef,
lazy => 0,
);
# Type of payment
has transaction_type => (
isa => Str,
is => 'rw',
required => 0,
predicate => 'has_transaction_type',
clearer => 'clear_transaction_type',
init_arg => undef,
lazy => 0,
);
# Business::CyberSource client object
has _client => (
isa => 'Business::CyberSource::Client',
is => 'bare',
builder => '_build_client',
required => 0,
predicate => 'has_client',
init_arg => undef,
handles => qr/^(?:run_transaction)$/x,
lazy => 1,
);
# Account username
has username => (
isa => NonEmptySimpleStr,
is => 'rw',
required => 0,
predicate => 'has_login',
alias => 'login',
lazy => 0,
);
# Account API key
has password => (
isa => Str,
is => 'rw',
required => 0,
predicate => 'has_password',
lazy => 0,
);
# Is this a test transaction?
has test_transaction => (
isa => Bool,
is => 'rw',
default => 0,
required => 0,
predicate => 'has_test_transaction',
trigger => sub {
my ( $self, $value ) = @_;
$self->clear_server() if $value;
return;
},
lazy => 1,
);
# Require address verification
has require_avs => (
isa => Bool,
is => 'rw',
default => 0,
required => 0,
predicate => 'has_require_avs',
lazy => 1,
);
# Remote server
has server => (
isa => NonEmptySimpleStr,
is => 'rw',
default => sub {
my ( $self ) = @_;
return ( $self->test_transaction() ) ? 'ics2wstest.ic3.com' : 'ics2ws.ic3.com';
},
required => 0,
predicate => 'has_server',
clearer => 'clear_server',
lazy => 1,
);
# Port for remote service
has port => (
isa => Int,
is => 'rw',
default => 443,
required => 0,
predicate => 'has_port',
lazy => 1,
);
# Path to remote service
has path => (
isa => NonEmptySimpleStr,
is => 'rw',
default => 'commerce/1.x/transactionProcessor',
required => 0,
predicate => 'has_path',
lazy => 1,
);
#### Method Modifiers ####
before qr/^(?:authorize|auth_reversal|capture|credit|sale)$/x, sub {
my ( $self ) = @_;
$self->_clear_fields();
return;
};
around qr/^(?:server|port|path)$/x, sub {
my ( $orig, $self, @args ) = @_;
Exception::Base->throw( 'Setting server, port, and or path information is not supported by this module' ) if ( scalar @args > 0 );
return $self->$orig( @args );
};
#### Consumed Roles ####
with
'Business::OnlinePayment::CyberSource::Role::InputHandling',
'Business::OnlinePayment::CyberSource::Role::ErrorReporting';
#### Meta class stuff ####
__PACKAGE__->meta->make_immutable();
1;
__END__
=pod
=head1 NAME
Business::OnlinePayment::CyberSource::Client - CyberSource Client object for Business::OnlinePayment::CyberSource
=head1 VERSION
version 3.000006
=head1 SYNOPSIS
use 5.010;
use Business::OnlinePayment::CyberSource::Client;
my $client = Business::OnlinePayment::CyberSource::Client->new();
my $data = {
invoice_number => 12345678,
purchase_totals => {
currency => 'USD',
total => 9000,
},
bill_to => {
first_name => 'Tofu',
last_name => 'Beast',
street1 => '123 Anystreet',
city => 'Anywhere',
state => 'UT',
postal_code => '84058',
country => 'US',
email => 'tofu@beast.org',
},
card => {
account_number => '4111111111111111',
expiration => { month => 12, year => 2012 },
security_code => 1111,
},
};
$client->authorize( $data );
if ( $client->is_success() ) {
say "Transaction succeeded!";
}
=head1 DESCRIPTION
Business::OnlinePayment::CyberSource::Client is a wrapper for the Business::CyberSource::Client. It provides a translation layer between the L<Business::OnlinePayment> API and the L<Business::CyberSource> API. While the input parameters and method names follow the conventions L<Business::CyberSource> API, the attribute names follow the L<Business::OnlinePayment> API.
=head1 ATTRIBUTES
=head2 is_success
use to determine whether or not the transaction succeeded
=head2 authorization
The authorization code supplied upon a successful authorization
=head2 order_number
This is the CyberSource-generated transaction identifier. It should be used to identify subsequent transactions to authorizations.
$client->capture( { ... service => { request_id => $client->order_number() }, ... } );
=head2 card_token
this is currently not supported.
=head2 fraud_score
This is currently not supported
=head2 fraud_transaction_id
This is currently not supported.
=head2 response_code
The HTTP response code
=head2 response_headers
A hash of the HTTP response headers
=head2 response_page
The HTTP response content
=head2 result_code
The processor response value
=head2 avs_code
The code returned for the Address Verification Service
=head2 cvv2_response
The CVV2 code value
=head2 transaction_type
This is the type value supplied to the content method of L<Business::OnlinePayment::CyberSource>
=head2 username
The CyberSource account username
=head2 password
The CyberSource account API key
=head2 test_transaction
Boolean value determining whether transactions should be sent as test transactions or not.
This method should be called after construction but before transactions are performed, unless it is supplied to the constructor.
=head2 require_avs
Boolean determining whether or not address verification should be done
=head2 server
This holds the value of the CyberSource server to which requests are being made.
=head2 port
This holds the port number on which the remote server is communicating.
=head2 path
This holds the path component of the remote service URI.
=head1 METHODS
=head2 authorize
This method should be used to perform an "Authorization Only" transaction.
Parameters:
{
reference_code => 44544,
bill_to => {
first_name => 'John",
last_name => 'Doe',
email => 'john.doe@example.com',
street1 => '101 Main Street',
city => 'Friendship',
state => 'AR',
zip => 12345,
country => 'US',
},
purchase_totals => {
total => 9000,
currency => 'USD',
},
}
Returns:
1 on success and 0 otherwise
=head2 sale
This method performs the "Normal Authorization" transaction. It combines "Authorization Only" and "Post Authorization".
Parameters:
{
reference_code => 44544,
bill_to => {
first_name => 'John",
last_name => 'Doe',
email => 'john.doe@example.com',
street1 => '101 Main Street',
city => 'Friendship',
state => 'AR',
zip => 12345,
country => 'US',
},
purchase_totals => {
total => 9000,
currency => 'USD',
},
}
Returns:
1 on success and 0 otherwise
=head2 credit
this method performs a "Credit" transaction.
Parameters:
(For typical credits)
{
reference_code => 44544,
bill_to => {
first_name => 'John",
last_name => 'Doe',
email => 'john.doe@example.com',
street1 => '101 Main Street',
city => 'Friendship',
state => 'AR',
zip => 12345,
country => 'US',
},
purchase_totals => {
total => 9000,
currency => 'USD',
},
}
(For follow-on credits)
{
reference_code => 44544,
service => { request_id => 1010101 }, # Generated by CyberSource
purchase_totals => {
total => 9000,
currency => 'USD',
},
}
Returns:
1 on success and 0 otherwise
=head2 capture
This method performs a "Post Authorization" transaction.
Parameters:
{
reference_code => 44544,
service => { request_id => 1010101 }, # Generated by CyberSource
purchase_totals => {
total => 9000,
currency => 'USD',
},
}
Returns:
1 on success and 0 otherwise
=head2 auth_reversal
This method performs a "Void" transaction
Parameters:
{
reference_code => 44544,
service => { request_id => 1010101 }, # Generated by CyberSource
purchase_totals => {
total => 9000,
currency => 'USD',
},
}
Returns:
1 on success and 0 otherwise
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
https://github.com/hgdev/Business-OnlinePayment-CyberSource/issues
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
=over 4
=item *
Jad Wauthier <Jadrien dot Wauthier at GMail dot com>
=item *
Caleb Cushing <xenoterracide@gmail.com>
=item *
Peter Bowen <peter@bowenfamily.org>
=back
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2012 by Hostgator.com.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut