summaryrefslogtreecommitdiff
path: root/FS/t/suite/13-tokenization.t
blob: 019b61c1b0b338689d460dde839eda7d4f8bfa63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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;