diff options
Diffstat (limited to 'FS/t/suite/13-tokenization.t')
-rw-r--r-- | FS/t/suite/13-tokenization.t | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/FS/t/suite/13-tokenization.t b/FS/t/suite/13-tokenization.t new file mode 100644 index 000000000..019b61c1b --- /dev/null +++ b/FS/t/suite/13-tokenization.t @@ -0,0 +1,224 @@ +#!/usr/bin/perl + +use strict; +use FS::Test; +use Test::More; +use FS::Conf; +use FS::cust_main; +use Business::CreditCard qw(generate_last_digit); +use DateTime; +if ( stat('/usr/local/etc/freeside/cardfortresstest.txt') ) { + plan tests => 21; +} else { + plan skip_all => 'CardFortress test encryption key is not installed.'; +} + +### can only run on test database (company name "Freeside Test") +### will run upgrade, which uses lots of prints & warns beyond regular test output + +my $fs = FS::Test->new( user => 'admin' ); +my $conf = FS::Conf->new; +my $err; +my $bopconf; + +like( $conf->config('company_name'), qr/^Freeside Test/, 'using test database' ) or BAIL_OUT(''); + +# upgrade just schema, or v3 test db cannot create refunds +$err = system('freeside-upgrade','-s','admin'); +ok( !$err, 'schema upgrade' ) or BAIL_OUT('Error string: '.$!); + +# some pre-upgrade cleanup, upgrade will fail if these are still configured +foreach my $cust_main ( $fs->qsearch('cust_main') ) { + my @count = $fs->qsearch('agent_payment_gateway', { agentnum => $cust_main->agentnum } ); + if (@count > 1) { + note("DELETING CARDTYPE GATEWAYS"); + foreach my $apg (@count) { + $err = $apg->delete if $apg->cardtype; + last if $err; + } + @count = $fs->qsearch('agent_payment_gateway', { agentnum => $cust_main->agentnum } ); + if (@count > 1) { + $err = "Still found ".@count." gateways for custnum ".$cust_main->custnum; + last; + } + } +} +ok( !$err, "remove obsolete payment gateways" ) or BAIL_OUT($err); + +$bopconf = +'IPPay +TESTTERMINAL'; +$conf->set('business-onlinepayment' => $bopconf); +is( join("\n",$conf->config('business-onlinepayment')), $bopconf, "setting first default gateway" ) or BAIL_OUT(''); + +# generate a few void/refund records for upgrading +my $counter = 20; +foreach my $cust_pay ( $fs->qsearch('cust_pay',{ payby => 'CARD' }) ) { + if ($counter % 2) { + $err = $cust_pay->void('Testing'); + $err = "Voiding: $err" if $err; + } else { + if ($fs->qsearch('cust_refund',{ source_paynum => $cust_pay->paynum })) { + note('refund skipping cust_pay '.$cust_pay->paynum.', already refunded'); + next; + } + # from realtime_refund_bop, just the important bits + while ( $cust_pay->unapplied < $cust_pay->paid ) { + my @cust_bill_pay = $cust_pay->cust_bill_pay; + last unless @cust_bill_pay; + my $cust_bill_pay = pop @cust_bill_pay; + $err = $cust_bill_pay->delete; + $err = "Refund unapply: $err" if $err; + last if $err; + } + last if $err; + my $cust_refund = new FS::cust_refund ( { + 'custnum' => $cust_pay->cust_main->custnum, + 'paynum' => $cust_pay->paynum, + 'source_paynum' => $cust_pay->paynum, + 'refund' => $cust_pay->paid, + '_date' => '', + 'payby' => $cust_pay->payby, + 'payinfo' => $cust_pay->payinfo, + 'reason' => 'Testing', + 'gatewaynum' => $cust_pay->gatewaynum, + 'processor' => $cust_pay->payment_gateway ? $cust_pay->payment_gateway->processor : '', + 'auth' => $cust_pay->auth, + 'order_number' => $cust_pay->order_number, + } ); + $err = $cust_refund->insert( reason_type => 'Refund' ); + $err = "Refunding ".$cust_pay->paynum.": $err" if $err; + } + last if $err; + $counter -= 1; + last unless $counter > 0; +} +$err ||= 'not enough records' if $counter; +ok( !$err, "create some refunds and voids" ) or BAIL_OUT($err); + +# also, just to test behavior in this case, create a record for an aborted +# verification payment. this will have no customer number. + +my $pending_failed = FS::cust_pay_pending->new({ + 'custnum_pending' => 1, + 'paid' => '1.00', + '_date' => time - 86400, + random_card(), + 'status' => 'failed', + 'statustext' => 'Tokenization upgrade test', +}); +$err = $pending_failed->insert; +ok( !$err, "create a failed payment attempt" ) or BAIL_OUT($err); + +# find two stored credit cards, and one stored checking account (to overwrite with CARD) +my @cust = ( + $fs->qsearch({ table=>'cust_main', hashref=>{payby=>'CARD'}, extra_sql=>' LIMIT 2' }), + $fs->qsearch({ table=>'cust_main', hashref=>{payby=>'CHEK'}, extra_sql=>' LIMIT 1' }), +); +my @payment; + +ok( $cust[0]->payby eq 'CARD' && !$cust[0]->tokenized, + "first customer has a non-tokenized card" + ) or BAIL_OUT(); + +$err = $cust[0]->realtime_bop({method => 'CC', amount => '2.00'}); +ok( !$err, "create a payment through IPPay" ) + or BAIL_OUT($err); +$payment[0] = $fs->qsearchs('cust_pay', { custnum => $cust[0]->custnum, + paid => '2.00' }) + or BAIL_OUT("can't find payment record"); + +$err = system('freeside-upgrade','admin'); +ok( !$err, 'initial upgrade' ) or BAIL_OUT('Error string: '.$!); + +# switch to CardFortress +$bopconf = +'CardFortress +cardfortresstest +(TEST54) +Normal Authorization +gateway +IPPay +gateway_login +TESTTERMINAL +gateway_password + +private_key +/usr/local/etc/freeside/cardfortresstest.txt'; +$conf->set('business-onlinepayment' => $bopconf); +is( join("\n",$conf->config('business-onlinepayment')), $bopconf, "setting tokenizable default gateway" ) or BAIL_OUT(''); + +foreach my $pg ($fs->qsearch('payment_gateway')) { + unless ($pg->gateway_module eq 'CardFortress') { + note('UPGRADING NON-CF PAYMENT GATEWAY'); + my %pgopts = ( + gateway => $pg->gateway_module, + gateway_login => $pg->gateway_username, + gateway_password => $pg->gateway_password, + private_key => '/usr/local/etc/freeside/cardfortresstest.txt', + ); + $pg->gateway_module('CardFortress'); + $pg->gateway_username('cardfortresstest'); + $pg->gateway_password('(TEST54)'); + $err = $pg->replace(\%pgopts); + last if $err; + } +} +ok( !$err, "remove non-CF payment gateways" ) or BAIL_OUT($err); + +# create a payment using a non-tokenized card. this should immediately +# trigger tokenization. +ok( $cust[1]->payby eq 'CARD' && ! $cust[1]->tokenized, + "second customer has a non-tokenized card" + ) or BAIL_OUT(); + +$err = $cust[1]->realtime_bop({method => 'CC', amount => '3.00'}); +ok( !$err, "tokenize a card when it's first used for payment" ) + or BAIL_OUT($err); +$payment[1] = $fs->qsearchs('cust_pay', { custnum => $cust[1]->custnum, + paid => '3.00' }) + or BAIL_OUT("can't find payment record"); +ok( $payment[1]->tokenized, "payment is tokenized" ); +$cust[1] = $cust[1]->replace_old; +ok( $cust[1]->tokenized, "card is now tokenized" ); + + +# invoke the part of freeside-upgrade that tokenizes +FS::cust_main->queueable_upgrade(); +#$err = system('freeside-upgrade','admin'); +#ok( !$err, 'tokenizable upgrade' ) or BAIL_OUT('Error string: '.$!); + +$cust[0] = $cust[0]->replace_old; +ok( $cust[0]->tokenized, "old card was tokenized during upgrade" ); +$payment[0] = $payment[0]->replace_old; +ok( $payment[0]->tokenized, "old payment was tokenized during upgrade" ); +my $old_pending = $fs->qsearchs('cust_pay_pending',{ paynum => $payment[0]->paynum }); +ok( $old_pending->tokenized, "old cust_pay_pending was tokenized during upgrade" ); + +$pending_failed = $pending_failed->replace_old; +ok( $pending_failed->tokenized, "cust_pay_pending with no customer was tokenized" ); + +# add a new payment card to one customer +my %newcard = random_card(); +$cust[2]->$_($newcard{$_}) foreach keys %newcard; +$err = $cust[2]->replace; +ok( !$err, "new card was saved" ) or BAIL_OUT($err); +ok($cust[2]->tokenized, "new card is tokenized" ); + +sub random_card { + my $payinfo = '4111' . join('', map { int(rand(10)) } 1 .. 11); + $payinfo .= generate_last_digit($payinfo); + my $paydate = DateTime->now + ->add('years' => 1) + ->truncate(to => 'month') + ->strftime('%F'); + return ( 'payby' => 'CARD', + 'payinfo' => $payinfo, + 'paydate' => $paydate, + 'payname' => 'Tokenize Me', + ); +} + +1; + + |