[freeside-commits] branch FREESIDE_4_BRANCH_71513 updated. 130db7caee3817758dcf3906d2e975e4eff0e466

Jonathan Prykop jonathan at 420.am
Mon Dec 12 10:16:44 PST 2016


The branch, FREESIDE_4_BRANCH_71513 has been updated
       via  130db7caee3817758dcf3906d2e975e4eff0e466 (commit)
       via  e5a0dc30df71b2a9490480c0333d88cec688c035 (commit)
      from  439ec8b79f5a430e4850d4620287e7774b4fb1e4 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 130db7caee3817758dcf3906d2e975e4eff0e466
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Mon Dec 12 12:06:38 2016 -0600

    RT#71513: Card tokenization [refund test expansion]

diff --git a/FS/t/suite/14-tokenization_refund.t b/FS/t/suite/14-tokenization_refund.t
index 65202fd..1a0f840 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;
 

commit e5a0dc30df71b2a9490480c0333d88cec688c035
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Fri Dec 9 12:45:01 2016 -0600

    71513: Card tokenization [refund testing & bug fixes]

diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm
index 0ef423b..28ed3c4 100644
--- a/FS/FS/cust_main/Billing_Realtime.pm
+++ b/FS/FS/cust_main/Billing_Realtime.pm
@@ -1454,9 +1454,10 @@ sub realtime_refund_bop {
       ( $gatewaynum, $processor, $auth, $order_number ) = ( $2, $3, $4, $6 );
     }
 
+    my $payment_gateway;
     if ( $gatewaynum ) { #gateway for the payment to be refunded
 
-      my $payment_gateway =
+      $payment_gateway =
         qsearchs('payment_gateway', { 'gatewaynum' => $gatewaynum } );
       die "payment gateway $gatewaynum not found"
         unless $payment_gateway;
@@ -1470,7 +1471,7 @@ sub realtime_refund_bop {
     } else { #try the default gateway
 
       my $conf_processor;
-      my $payment_gateway =
+      $payment_gateway =
         $self->agent->payment_gateway('method' => $options{method});
 
       ( $conf_processor, $login, $password, $namespace ) =
@@ -1487,8 +1488,27 @@ sub realtime_refund_bop {
         unless ($processor eq $conf_processor)
             || (($conf_processor eq 'CardFortress') && ($processor eq $bop_options{'gateway'}));
 
+      $processor = $conf_processor;
+
     }
 
+    # if gateway has switched to CardFortress but token_check hasn't run yet,
+    # tokenize just this record now, so that token gets passed/set appropriately
+    if ($cust_pay->payby eq 'CARD' && !$cust_pay->tokenized) {
+      my %tokenopts = (
+        'payment_gateway' => $payment_gateway,
+        'method'          => 'CC',
+        'payinfo'         => $cust_pay->payinfo,
+        'paydate'         => $cust_pay->paydate,
+      );
+      my $error = $self->realtime_tokenize(\%tokenopts); # no-op unless gateway can tokenize
+      if ($self->tokenized($tokenopts{'payinfo'})) { # implies no error
+        warn "  tokenizing cust_pay\n" if $DEBUG > 1;
+        $cust_pay->payinfo($tokenopts{'payinfo'});
+        $error = $cust_pay->replace;
+      }
+      return $error if $error;
+    }
 
   } else { # didn't specify a paynum, so look for agent gateway overrides
            # like a normal transaction 
@@ -1567,6 +1587,12 @@ sub realtime_refund_bop {
         $content{'name'} = $self->get('first'). ' '. $self->get('last');
       }
     }
+    if ( $cust_pay->payby eq 'CARD'
+         && !$content{'card_number'}
+         && $cust_pay->tokenized
+    ) {
+      $content{'card_token'} = $cust_pay->payinfo;
+    }
     $void->content( 'action' => 'void', %content );
     $void->test_transaction(1)
       if $conf->exists('business-onlinepayment-test_transaction');
diff --git a/FS/t/suite/14-tokenization_refund.t b/FS/t/suite/14-tokenization_refund.t
new file mode 100755
index 0000000..65202fd
--- /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;
+

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/cust_main/Billing_Realtime.pm |   30 ++++-
 FS/t/suite/14-tokenization_refund.t |  246 +++++++++++++++++++++++++++++++++++
 2 files changed, 274 insertions(+), 2 deletions(-)
 create mode 100755 FS/t/suite/14-tokenization_refund.t




More information about the freeside-commits mailing list