From 3d8958a36f22a88738b637b4d5583e989e91bc8e Mon Sep 17 00:00:00 2001 From: Jonathan Prykop Date: Tue, 29 Nov 2016 04:21:46 -0600 Subject: 71513: Card tokenization [upgrade implemented] --- FS/t/suite/13-tokenization.t | 82 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100755 FS/t/suite/13-tokenization.t (limited to 'FS/t/suite') diff --git a/FS/t/suite/13-tokenization.t b/FS/t/suite/13-tokenization.t new file mode 100755 index 000000000..1b654add5 --- /dev/null +++ b/FS/t/suite/13-tokenization.t @@ -0,0 +1,82 @@ +#!/usr/bin/perl + +use FS::Test; +use Test::More tests => 8; +use FS::Conf; + +### 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 = new_ok('FS::Conf'); +my $err; +my $bopconf; + +like( $conf->config('company_name'), qr/^Freeside Test/, 'using test database' ) or BAIL_OUT(''); + +# 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(''); + +$err = system('freeside-upgrade','admin'); +ok( !$err, 'initial upgrade' ) or BAIL_OUT('Error string: '.$!); + +$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); + +$err = system('freeside-upgrade','admin'); +ok( !$err, 'tokenizable upgrade' ) or BAIL_OUT('Error string: '.$!); + +1; + -- cgit v1.2.1 From 9547ad4e7cc87b1cfe6bc9f2428da81481e18926 Mon Sep 17 00:00:00 2001 From: Jonathan Prykop Date: Wed, 30 Nov 2016 05:45:29 -0600 Subject: 71513: Card tokenization [bug fixes, removed cardtype/taxclass override ui] --- FS/t/suite/13-tokenization.t | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) (limited to 'FS/t/suite') diff --git a/FS/t/suite/13-tokenization.t b/FS/t/suite/13-tokenization.t index 1b654add5..9a3ef3f22 100755 --- a/FS/t/suite/13-tokenization.t +++ b/FS/t/suite/13-tokenization.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use FS::Test; -use Test::More tests => 8; +use Test::More tests => 9; use FS::Conf; ### can only run on test database (company name "Freeside Test") @@ -38,6 +38,46 @@ 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 { + # 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: $err" if $err; + } + last if $err; + $counter -= 1; + last unless $counter > 0; +} +ok( !$err, "create some refunds and voids" ) or BAIL_OUT($err); + $err = system('freeside-upgrade','admin'); ok( !$err, 'initial upgrade' ) or BAIL_OUT('Error string: '.$!); -- cgit v1.2.1 From b1fac2d6e401888a496ca1ac48fae1f46498333b Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Sun, 4 Dec 2016 23:13:04 -0800 Subject: rework card tokenization test --- FS/t/suite/13-tokenization.t | 150 ++++++++++++++++++++++++++++++++----------- 1 file changed, 113 insertions(+), 37 deletions(-) (limited to 'FS/t/suite') diff --git a/FS/t/suite/13-tokenization.t b/FS/t/suite/13-tokenization.t index 9a3ef3f22..b4d204f89 100755 --- a/FS/t/suite/13-tokenization.t +++ b/FS/t/suite/13-tokenization.t @@ -1,36 +1,29 @@ #!/usr/bin/perl +use strict; use FS::Test; -use Test::More tests => 9; +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 => 18; +} 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 = new_ok('FS::Conf'); +my $conf = FS::Conf->new; my $err; my $bopconf; like( $conf->config('company_name'), qr/^Freeside Test/, 'using test database' ) or BAIL_OUT(''); -# 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); +# test db no longer contains cardtype overrides $bopconf = 'IPPay @@ -78,9 +71,40 @@ foreach my $cust_pay ( $fs->qsearch('cust_pay',{ payby => 'CARD' }) ) { } 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. +my @cust = map { FS::cust_main->by_key($_) } (10, 12); +my @payby = map { ($_->cust_payby)[0] } @cust; +my @payment; + +ok( $payby[0]->payby eq 'CARD' && !$payby[0]->tokenized, + "first customer has a non-tokenized card" + ) or BAIL_OUT(); + +$err = $cust[0]->realtime_cust_payby(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 @@ -97,26 +121,78 @@ private_key $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; - } +# create a payment using a non-tokenized card. this should immediately +# trigger tokenization. +ok( $payby[1]->payby eq 'CARD' && ! $payby[1]->tokenized, + "second customer has a non-tokenized card" + ) or BAIL_OUT(); + +$err = $cust[1]->realtime_cust_payby(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" ); +$payby[1] = $payby[1]->replace_old; +ok( $payby[1]->tokenized, "card is now tokenized" ); + +# test db doesn't have this +#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); + +# invoke the part of freeside-upgrade that tokenizes +FS::cust_main->queueable_upgrade( quiet => 1 ); +#$err = system('freeside-upgrade','admin'); +#ok( !$err, 'tokenizable upgrade' ) or BAIL_OUT('Error string: '.$!); + +$payby[0] = $payby[0]->replace_old; +ok( $payby[0]->tokenized, "old card was tokenized during upgrade" ); +$payment[0] = $payment[0]->replace_old; +ok( $payment[0]->tokenized, "old payment was tokenized during upgrade" ); +ok( ($payment[0]->cust_pay_pending)[0]->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 +$payby[2] = FS::cust_payby->new({ + custnum => $cust[0]->custnum, + random_card(), +}); +$err = $payby[2]->insert; +ok( !$err, "new card was saved" ); +ok($payby[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', + ); } -ok( !$err, "remove non-CF payment gateways" ) or BAIL_OUT($err); -$err = system('freeside-upgrade','admin'); -ok( !$err, 'tokenizable upgrade' ) or BAIL_OUT('Error string: '.$!); 1; -- cgit v1.2.1 From 6bf73cace9d0cc630f54ec8b2cdb2d0fb6132cf5 Mon Sep 17 00:00:00 2001 From: Jonathan Prykop Date: Mon, 5 Dec 2016 08:53:13 -0600 Subject: 71513: Card tokenization [minor test tweaks] --- FS/t/suite/13-tokenization.t | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) (limited to 'FS/t/suite') diff --git a/FS/t/suite/13-tokenization.t b/FS/t/suite/13-tokenization.t index b4d204f89..0a965aa87 100755 --- a/FS/t/suite/13-tokenization.t +++ b/FS/t/suite/13-tokenization.t @@ -137,27 +137,8 @@ ok( $payment[1]->tokenized, "payment is tokenized" ); $payby[1] = $payby[1]->replace_old; ok( $payby[1]->tokenized, "card is now tokenized" ); -# test db doesn't have this -#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); - # invoke the part of freeside-upgrade that tokenizes -FS::cust_main->queueable_upgrade( quiet => 1 ); +FS::cust_main->queueable_upgrade(); #$err = system('freeside-upgrade','admin'); #ok( !$err, 'tokenizable upgrade' ) or BAIL_OUT('Error string: '.$!); -- cgit v1.2.1 From 439ec8b79f5a430e4850d4620287e7774b4fb1e4 Mon Sep 17 00:00:00 2001 From: Jonathan Prykop Date: Mon, 5 Dec 2016 10:06:10 -0600 Subject: 71513: Card tokenization [v4 test db handling] --- FS/t/suite/13-tokenization.t | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) (limited to 'FS/t/suite') diff --git a/FS/t/suite/13-tokenization.t b/FS/t/suite/13-tokenization.t index 0a965aa87..edb0f3896 100755 --- a/FS/t/suite/13-tokenization.t +++ b/FS/t/suite/13-tokenization.t @@ -8,7 +8,7 @@ use FS::cust_main; use Business::CreditCard qw(generate_last_digit); use DateTime; if ( stat('/usr/local/etc/freeside/cardfortresstest.txt') ) { - plan tests => 18; + plan tests => 20; } else { plan skip_all => 'CardFortress test encryption key is not installed.'; } @@ -23,7 +23,23 @@ my $bopconf; like( $conf->config('company_name'), qr/^Freeside Test/, 'using test database' ) or BAIL_OUT(''); -# test db no longer contains cardtype overrides +# 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 @@ -121,6 +137,24 @@ private_key $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( $payby[1]->payby eq 'CARD' && ! $payby[1]->tokenized, -- cgit v1.2.1 From e5a0dc30df71b2a9490480c0333d88cec688c035 Mon Sep 17 00:00:00 2001 From: Jonathan Prykop Date: Fri, 9 Dec 2016 12:45:01 -0600 Subject: 71513: Card tokenization [refund testing & bug fixes] --- FS/t/suite/14-tokenization_refund.t | 200 ++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100755 FS/t/suite/14-tokenization_refund.t (limited to 'FS/t/suite') diff --git a/FS/t/suite/14-tokenization_refund.t b/FS/t/suite/14-tokenization_refund.t new file mode 100755 index 000000000..65202fdfd --- /dev/null +++ b/FS/t/suite/14-tokenization_refund.t @@ -0,0 +1,200 @@ +#!/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 => 33; +} else { + plan skip_all => 'CardFortress test encryption key is not installed.'; +} + +#local $FS::cust_main::Billing_Realtime::DEBUG = 2; + +my $fs = FS::Test->new( user => 'admin' ); +my $conf = FS::Conf->new; +my $err; +my @bopconf; + +### can only run on test database (company name "Freeside Test") +like( $conf->config('company_name'), qr/^Freeside Test/, 'using test database' ) or BAIL_OUT(''); + +### database might need to be upgraded before this, +### but doesn't matter if existing records are tokenized or not, +### this is all about testing new record creation + +# these will just get in the way for now +foreach my $apg ($fs->qsearch('agent_payment_gateway')) { + $err = $apg->delete; + last if $err; +} +ok( !$err, 'removing agent gateway overrides' ) or BAIL_OUT($err); + +# will need this +my $reason = FS::reason->new_or_existing( + reason => 'Token Test', + type => 'Refund', + class => 'F', +); +isa_ok ( $reason, 'FS::reason', "refund reason" ) or BAIL_OUT(''); + +# non-tokenizing gateway +push @bopconf, +'IPPay +TESTTERMINAL'; + +# tokenizing gateway +push @bopconf, +'CardFortress +cardfortresstest +(TEST54) +Normal Authorization +gateway +IPPay +gateway_login +TESTTERMINAL +gateway_password + +private_key +/usr/local/etc/freeside/cardfortresstest.txt'; + +# for attempting refund post-tokenization +my $n_cust_main; +my $n_cust_pay; + +foreach my $tokenizing (0,1) { + + my $adj = $tokenizing ? 'tokenizable' : 'non-tokenizable'; + + # set payment gateway + $conf->set('business-onlinepayment' => $bopconf[$tokenizing]); + is( join("\n",$conf->config('business-onlinepayment')), $bopconf[$tokenizing], "set $adj default gateway" ) or BAIL_OUT(''); + + if ($tokenizing) { + + my $n_paynum = $n_cust_pay->paynum; + + # refund the previous non-tokenized payment through CF + $err = $n_cust_main->realtime_refund_bop({ + reasonnum => $reason->reasonnum, + paynum => $n_paynum, + method => 'CC', + }); + ok( !$err, "run post-switch refund" ) or BAIL_OUT($err); + + # check for void record + my $n_cust_pay_void = $fs->qsearchs('cust_pay_void',{ paynum => $n_paynum }); + isa_ok( $n_cust_pay_void, 'FS::cust_pay_void', 'post-switch void') or BAIL_OUT("paynum $n_paynum"); + + # check that void tokenized + ok ( $n_cust_pay_void->tokenized, "post-switch void tokenized" ) or BAIL_OUT("paynum $n_paynum"); + + # check for no refund record + ok( !$fs->qsearch('cust_refund',{ source_paynum => $n_paynum }), "post-switch refund did not generate cust_refund" ) or BAIL_OUT("paynum $n_paynum"); + + } + + # create customer + my $cust_main = $fs->new_customer($adj); + isa_ok ( $cust_main, 'FS::cust_main', "$adj customer" ) or BAIL_OUT(''); + + # insert customer + $err = $cust_main->insert; + ok( !$err, "insert $adj customer" ) or BAIL_OUT($err); + + # add card + my $cust_payby; + my %card = random_card(); + $err = $cust_main->save_cust_payby( + %card, + payment_payby => $card{'payby'}, + auto => 1, + saved_cust_payby => \$cust_payby + ); + ok( !$err, "save $adj card" ) or BAIL_OUT($err); + + # retrieve card + isa_ok ( $cust_payby, 'FS::cust_payby', "$adj card" ) or BAIL_OUT(''); + + # check that card tokenized or not + if ($tokenizing) { + ok( $cust_payby->tokenized, 'new cust card tokenized' ) or BAIL_OUT(''); + } else { + ok( !$cust_payby->tokenized, 'new cust card not tokenized' ) or BAIL_OUT(''); + } + + # run a payment + $err = $cust_main->realtime_cust_payby( amount => '1.00' ); + ok( !$err, "run $adj payment" ) or BAIL_OUT($err); + + # get the payment + my $cust_pay = $fs->qsearchs('cust_pay',{ custnum => $cust_main->custnum }); + isa_ok ( $cust_pay, 'FS::cust_pay', "$adj payment" ) or BAIL_OUT(''); + + # refund the payment + $err = $cust_main->realtime_refund_bop({ + reasonnum => $reason->reasonnum, + paynum => $cust_pay->paynum, + method => 'CC', + }); + ok( !$err, "run $adj refund" ) or BAIL_OUT($err); + + unless ($tokenizing) { + + # run a second payment, to refund after switch + $err = $cust_main->realtime_cust_payby( amount => '2.00' ); + ok( !$err, "run $adj second payment" ) or BAIL_OUT($err); + + # get the second payment + $n_cust_pay = $fs->qsearchs('cust_pay',{ custnum => $cust_main->custnum, paid => '2.00' }); + isa_ok ( $n_cust_pay, 'FS::cust_pay', "$adj second payment" ) or BAIL_OUT(''); + + $n_cust_main = $cust_main; + + } + + #check that all transactions tokenized or not + foreach my $table (qw(cust_pay_pending cust_pay cust_pay_void)) { + foreach my $record ($fs->qsearch($table,{ custnum => $cust_main->custnum })) { + if ($tokenizing) { + $err = "record not tokenized: $table ".$record->get($record->primary_key) + unless $record->tokenized; + } else { + $err = "record tokenized: $table ".$record->get($record->primary_key) + if $record->tokenized; + } + last if $err; + } + } + ok( !$err, "$adj transaction token check" ) or BAIL_OUT($err); + + #make sure we voided + ok( $fs->qsearch('cust_pay_void',{ custnum => $cust_main->custnum}), "$adj refund voided" ) or BAIL_OUT(''); + + #make sure we didn't generate refund records + ok( !$fs->qsearch('cust_refund',{ custnum => $cust_main->custnum}), "$adj refund did not generate cust_refund" ) or BAIL_OUT(''); + +}; + +exit; + +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; + -- cgit v1.2.1 From 130db7caee3817758dcf3906d2e975e4eff0e466 Mon Sep 17 00:00:00 2001 From: Jonathan Prykop Date: Mon, 12 Dec 2016 12:06:38 -0600 Subject: RT#71513: Card tokenization [refund test expansion] --- FS/t/suite/14-tokenization_refund.t | 236 +++++++++++++++++++++--------------- 1 file changed, 141 insertions(+), 95 deletions(-) (limited to 'FS/t/suite') diff --git a/FS/t/suite/14-tokenization_refund.t b/FS/t/suite/14-tokenization_refund.t index 65202fdfd..1a0f8405e 100755 --- a/FS/t/suite/14-tokenization_refund.t +++ b/FS/t/suite/14-tokenization_refund.t @@ -8,7 +8,7 @@ use FS::cust_main; use Business::CreditCard qw(generate_last_digit); use DateTime; if ( stat('/usr/local/etc/freeside/cardfortresstest.txt') ) { - plan tests => 33; + plan tests => 66; } else { plan skip_all => 'CardFortress test encryption key is not installed.'; } @@ -23,10 +23,6 @@ my @bopconf; ### can only run on test database (company name "Freeside Test") like( $conf->config('company_name'), qr/^Freeside Test/, 'using test database' ) or BAIL_OUT(''); -### database might need to be upgraded before this, -### but doesn't matter if existing records are tokenized or not, -### this is all about testing new record creation - # these will just get in the way for now foreach my $apg ($fs->qsearch('agent_payment_gateway')) { $err = $apg->delete; @@ -62,123 +58,173 @@ gateway_password private_key /usr/local/etc/freeside/cardfortresstest.txt'; -# for attempting refund post-tokenization -my $n_cust_main; -my $n_cust_pay; +foreach my $voiding (0,1) { + my $noun = $voiding ? 'void' : 'refund'; -foreach my $tokenizing (0,1) { + if ($voiding) { + $conf->delete('disable_void_after'); + ok( !$conf->exists('disable_void_after'), 'set disable_void_after to produce voids' ) or BAIL_OUT(''); + } else { + $conf->set('disable_void_after' => '0'); + is( $conf->config('disable_void_after'), '0', 'set disable_void_after to produce refunds' ) or BAIL_OUT(''); + } - my $adj = $tokenizing ? 'tokenizable' : 'non-tokenizable'; + # for attempting refund post-tokenization + my $n_cust_main; + my $n_cust_pay; - # set payment gateway - $conf->set('business-onlinepayment' => $bopconf[$tokenizing]); - is( join("\n",$conf->config('business-onlinepayment')), $bopconf[$tokenizing], "set $adj default gateway" ) or BAIL_OUT(''); + foreach my $tokenizing (0,1) { + my $adj = $tokenizing ? 'tokenizable' : 'non-tokenizable'; - if ($tokenizing) { + # set payment gateway + $conf->set('business-onlinepayment' => $bopconf[$tokenizing]); + is( join("\n",$conf->config('business-onlinepayment')), $bopconf[$tokenizing], "set $adj $noun default gateway" ) or BAIL_OUT(''); - my $n_paynum = $n_cust_pay->paynum; + # make sure we're upgraded, only need to do it once, + # use non-tokenizing gateway for speed, + # but doesn't matter if existing records are tokenized or not, + # this suite is all about testing new record creation + if (!$tokenizing && !$voiding) { + $err = system('freeside-upgrade','-q','admin'); + ok( !$err, 'upgrade freeside' ) or BAIL_OUT('Error string: '.$!); + } - # refund the previous non-tokenized payment through CF - $err = $n_cust_main->realtime_refund_bop({ - reasonnum => $reason->reasonnum, - paynum => $n_paynum, - method => 'CC', - }); - ok( !$err, "run post-switch refund" ) or BAIL_OUT($err); + if ($tokenizing) { - # check for void record - my $n_cust_pay_void = $fs->qsearchs('cust_pay_void',{ paynum => $n_paynum }); - isa_ok( $n_cust_pay_void, 'FS::cust_pay_void', 'post-switch void') or BAIL_OUT("paynum $n_paynum"); + my $n_paynum = $n_cust_pay->paynum; - # check that void tokenized - ok ( $n_cust_pay_void->tokenized, "post-switch void tokenized" ) or BAIL_OUT("paynum $n_paynum"); + # refund the previous non-tokenized payment through CF + $err = $n_cust_main->realtime_refund_bop({ + reasonnum => $reason->reasonnum, + paynum => $n_paynum, + method => 'CC', + }); + ok( !$err, "run post-switch $noun" ) or BAIL_OUT($err); - # check for no refund record - ok( !$fs->qsearch('cust_refund',{ source_paynum => $n_paynum }), "post-switch refund did not generate cust_refund" ) or BAIL_OUT("paynum $n_paynum"); + my $n_cust_pay_void = $fs->qsearchs('cust_pay_void',{ paynum => $n_paynum }); + my $n_cust_refund = $fs->qsearchs('cust_refund',{ source_paynum => $n_paynum }); - } + if ($voiding) { - # create customer - my $cust_main = $fs->new_customer($adj); - isa_ok ( $cust_main, 'FS::cust_main', "$adj customer" ) or BAIL_OUT(''); - - # insert customer - $err = $cust_main->insert; - ok( !$err, "insert $adj customer" ) or BAIL_OUT($err); - - # add card - my $cust_payby; - my %card = random_card(); - $err = $cust_main->save_cust_payby( - %card, - payment_payby => $card{'payby'}, - auto => 1, - saved_cust_payby => \$cust_payby - ); - ok( !$err, "save $adj card" ) or BAIL_OUT($err); + # check for void record + isa_ok( $n_cust_pay_void, 'FS::cust_pay_void', 'post-switch void') or BAIL_OUT("paynum $n_paynum"); - # retrieve card - isa_ok ( $cust_payby, 'FS::cust_payby', "$adj card" ) or BAIL_OUT(''); + # check that void tokenized + ok ( $n_cust_pay_void->tokenized, "post-switch void tokenized" ) or BAIL_OUT("paynum $n_paynum"); - # check that card tokenized or not - if ($tokenizing) { - ok( $cust_payby->tokenized, 'new cust card tokenized' ) or BAIL_OUT(''); - } else { - ok( !$cust_payby->tokenized, 'new cust card not tokenized' ) or BAIL_OUT(''); - } + # check for no refund record + ok( !$n_cust_refund, "post-switch void did not generate cust_refund" ) or BAIL_OUT("paynum $n_paynum"); + + } else { + + # check for refund record + isa_ok( $n_cust_refund, 'FS::cust_refund', 'post-switch refund') or BAIL_OUT("paynum $n_paynum"); + + # check that refund tokenized + ok ( $n_cust_refund->tokenized, "post-switch refund tokenized" ) or BAIL_OUT("paynum $n_paynum"); + + # check for no refund record + ok( !$n_cust_pay_void, "post-switch refund did not generate cust_pay_void" ) or BAIL_OUT("paynum $n_paynum"); + + } - # run a payment - $err = $cust_main->realtime_cust_payby( amount => '1.00' ); - ok( !$err, "run $adj payment" ) or BAIL_OUT($err); + } - # get the payment - my $cust_pay = $fs->qsearchs('cust_pay',{ custnum => $cust_main->custnum }); - isa_ok ( $cust_pay, 'FS::cust_pay', "$adj payment" ) or BAIL_OUT(''); + # create customer + my $cust_main = $fs->new_customer($adj.'X'.$noun); + isa_ok ( $cust_main, 'FS::cust_main', "$adj $noun customer" ) or BAIL_OUT(''); + + # insert customer + $err = $cust_main->insert; + ok( !$err, "insert $adj $noun customer" ) or BAIL_OUT($err); + + # add card + my $cust_payby; + my %card = random_card(); + $err = $cust_main->save_cust_payby( + %card, + payment_payby => $card{'payby'}, + auto => 1, + saved_cust_payby => \$cust_payby + ); + ok( !$err, "save $adj $noun card" ) or BAIL_OUT($err); + + # retrieve card + isa_ok ( $cust_payby, 'FS::cust_payby', "$adj $noun card" ) or BAIL_OUT(''); + + # check that card tokenized or not + if ($tokenizing) { + ok( $cust_payby->tokenized, "new $noun cust card tokenized" ) or BAIL_OUT(''); + } else { + ok( !$cust_payby->tokenized, "new $noun cust card not tokenized" ) or BAIL_OUT(''); + } - # refund the payment - $err = $cust_main->realtime_refund_bop({ - reasonnum => $reason->reasonnum, - paynum => $cust_pay->paynum, - method => 'CC', - }); - ok( !$err, "run $adj refund" ) or BAIL_OUT($err); + # run a payment + $err = $cust_main->realtime_cust_payby( amount => '1.00' ); + ok( !$err, "run $adj $noun payment" ) or BAIL_OUT($err); - unless ($tokenizing) { + # get the payment + my $cust_pay = $fs->qsearchs('cust_pay',{ custnum => $cust_main->custnum }); + isa_ok ( $cust_pay, 'FS::cust_pay', "$adj $noun payment" ) or BAIL_OUT(''); - # run a second payment, to refund after switch - $err = $cust_main->realtime_cust_payby( amount => '2.00' ); - ok( !$err, "run $adj second payment" ) or BAIL_OUT($err); + # refund the payment + $err = $cust_main->realtime_refund_bop({ + reasonnum => $reason->reasonnum, + paynum => $cust_pay->paynum, + method => 'CC', + }); + ok( !$err, "run $adj $noun" ) or BAIL_OUT($err); + + unless ($tokenizing) { + + # run a second payment, to refund after switch + $err = $cust_main->realtime_cust_payby( amount => '2.00' ); + ok( !$err, "run $adj $noun second payment" ) or BAIL_OUT($err); - # get the second payment - $n_cust_pay = $fs->qsearchs('cust_pay',{ custnum => $cust_main->custnum, paid => '2.00' }); - isa_ok ( $n_cust_pay, 'FS::cust_pay', "$adj second payment" ) or BAIL_OUT(''); + # get the second payment + $n_cust_pay = $fs->qsearchs('cust_pay',{ custnum => $cust_main->custnum, paid => '2.00' }); + isa_ok ( $n_cust_pay, 'FS::cust_pay', "$adj $noun second payment" ) or BAIL_OUT(''); - $n_cust_main = $cust_main; + $n_cust_main = $cust_main; - } + } - #check that all transactions tokenized or not - foreach my $table (qw(cust_pay_pending cust_pay cust_pay_void)) { - foreach my $record ($fs->qsearch($table,{ custnum => $cust_main->custnum })) { - if ($tokenizing) { - $err = "record not tokenized: $table ".$record->get($record->primary_key) - unless $record->tokenized; - } else { - $err = "record tokenized: $table ".$record->get($record->primary_key) - if $record->tokenized; + #check that all transactions tokenized or not + foreach my $table (qw(cust_pay_pending cust_pay cust_pay_void cust_refund)) { + foreach my $record ($fs->qsearch($table,{ custnum => $cust_main->custnum })) { + if ($tokenizing) { + $err = "record not tokenized: $table ".$record->get($record->primary_key) + unless $record->tokenized; + } else { + $err = "record tokenized: $table ".$record->get($record->primary_key) + if $record->tokenized; + } + last if $err; } - last if $err; } - } - ok( !$err, "$adj transaction token check" ) or BAIL_OUT($err); + ok( !$err, "$adj transaction token check" ) or BAIL_OUT($err); + + if ($voiding) { + + #make sure we voided + ok( $fs->qsearch('cust_pay_void',{ custnum => $cust_main->custnum}), "$adj $noun record found" ) or BAIL_OUT(''); + + #make sure we didn't generate refund records + ok( !$fs->qsearch('cust_refund',{ custnum => $cust_main->custnum}), "$adj $noun did not generate cust_refund" ) or BAIL_OUT(''); - #make sure we voided - ok( $fs->qsearch('cust_pay_void',{ custnum => $cust_main->custnum}), "$adj refund voided" ) or BAIL_OUT(''); + } else { + + #make sure we refunded + ok( $fs->qsearch('cust_refund',{ custnum => $cust_main->custnum}), "$adj $noun record found" ) or BAIL_OUT(''); + + #make sure we didn't generate void records + ok( !$fs->qsearch('cust_pay_void',{ custnum => $cust_main->custnum}), "$adj $noun did not generate cust_pay_void" ) or BAIL_OUT(''); + + } - #make sure we didn't generate refund records - ok( !$fs->qsearch('cust_refund',{ custnum => $cust_main->custnum}), "$adj refund did not generate cust_refund" ) or BAIL_OUT(''); + } #end of tokenizing or not -}; +} # end of voiding or not exit; -- cgit v1.2.1