#!/usr/bin/perl
+use strict;
use FS::Test;
-use Test::More tests => 8;
+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 => 25;
+} else {
+ plan skip_all => 'CardFortress test encryption key is not installed.';
+}
+
+#local $FS::cust_main::Billing_Realtime::DEBUG = 2;
### 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
$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);
+
+# 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);
+
+# create two customers with an AmEx card & paycvv,
+# then run a payment with one, just to generate some test AmEx data
+
+my $amex_cust;
+foreach my $i (0,1) {
+ my $cust_main = $fs->new_customer("AmEx $i");
+ isa_ok ( $cust_main, 'FS::cust_main', "AmEx $i customer" ) or BAIL_OUT('');
+ $err = $cust_main->insert;
+ ok( !$err, "insert AmEx $i customer" ) or BAIL_OUT($err);
+ # add card
+ my $cust_payby;
+ my %card = random_card();
+ $card{'payinfo'} = '347594362484937';
+ $card{'paycvv'} = '1234';
+ $err = $cust_main->save_cust_payby(
+ %card,
+ payment_payby => $card{'payby'},
+ auto => 1,
+ saved_cust_payby => \$cust_payby
+ );
+ ok( !$err, "save AmEx $i card" ) or BAIL_OUT($err);
+ $amex_cust = $cust_main;
+}
+$err = $amex_cust->realtime_cust_payby( amount => '1.00' );
+ok( !$err, "run AmEx payment" ) 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
$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" );
+
+# 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: '.$!);
+
+$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;