[freeside-commits] branch master updated. 2a82381a976c22f2c0d85645e8b327713ddcbd88

Christopher Burger burgerc at freeside.biz
Wed Oct 10 09:56:56 PDT 2018


The branch, master has been updated
       via  2a82381a976c22f2c0d85645e8b327713ddcbd88 (commit)
      from  29445ff3f69c32ba0f836f3c5bbaf946c154b360 (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 2a82381a976c22f2c0d85645e8b327713ddcbd88
Author: Christopher Burger <burgerc at freeside.biz>
Date:   Wed Oct 10 12:53:53 2018 -0400

    RT# 39340 - created access to payment only via ip address, fixes security by creating a seperate session

diff --git a/FS/FS/ClientAPI/MasonComponent.pm b/FS/FS/ClientAPI/MasonComponent.pm
index d615c271c..e216d919a 100644
--- a/FS/FS/ClientAPI/MasonComponent.pm
+++ b/FS/FS/ClientAPI/MasonComponent.pm
@@ -2,7 +2,7 @@ package FS::ClientAPI::MasonComponent;
 
 use strict;
 use vars qw( $cache $DEBUG $me );
-use subs qw( _cache );
+use subs qw( _cache _mason_comp );
 use FS::Mason qw( mason_interps );
 use FS::Conf;
 use FS::ClientAPI_SessionCache;
@@ -138,8 +138,27 @@ my( $fs_interp, $rt_interp ) = mason_interps('standalone', 'outbuf'=>\$outbuf);
 
 sub mason_comp {
   my $packet = shift;
+  my $me = 'mason_comp';
+  my $namespace = 'FS::ClientAPI::MyAccount';
 
-  warn "$me mason_comp called on $packet\n" if $DEBUG;
+  _mason_comp($packet, $me, $namespace);
+
+}
+
+sub payment_only_mason_comp {
+  my $packet = shift;
+  my $me = 'payment_only_mason_comp';
+  my $namespace = 'FS::ClientAPI::PaymentOnly';
+
+  _mason_comp($packet, $me, $namespace);
+}
+
+sub _mason_comp {
+  my $packet = shift;
+  my $me = shift;
+  my $namespace = shift;
+
+  warn "$me called on $packet\n" if $DEBUG;
 
   my $comp = $packet->{'comp'};
   unless ( $allowed_comps{$comp} || $session_comps{$comp} ) {
@@ -150,7 +169,7 @@ sub mason_comp {
 
   if ( $session_comps{$comp} ) {
 
-    my $session = _cache->get($packet->{'session_id'})
+    my $session = _cache($namespace)->get($packet->{'session_id'})
       or return ( 'error' => "Can't resume session" ); #better error message
     my $custnum = $session->{'custnum'};
 
@@ -175,8 +194,10 @@ sub mason_comp {
 
 #hmm
 sub _cache {
+  my $namespace = shift || 'FS::ClientAPI::MyAccount';
+
   $cache ||= new FS::ClientAPI_SessionCache( {
-               'namespace' => 'FS::ClientAPI::MyAccount',
+               'namespace' => $namespace,
              } );
 }
 
diff --git a/FS/FS/ClientAPI/PaymentOnly.pm b/FS/FS/ClientAPI/PaymentOnly.pm
new file mode 100644
index 000000000..e1c59a91b
--- /dev/null
+++ b/FS/FS/ClientAPI/PaymentOnly.pm
@@ -0,0 +1,472 @@
+package FS::ClientAPI::PaymentOnly;
+
+use 5.008; #require 5.8+ for Time::Local 1.05+
+use strict;
+use vars qw( $cache $DEBUG $me );
+use subs qw( _cache _provision );
+use FS::ClientAPI_SessionCache;
+
+use IO::Scalar;
+use Data::Dumper;
+use Digest::MD5 qw(md5_hex);
+use Digest::SHA qw(sha512_hex);
+use Date::Format;
+use Time::Duration;
+use Time::Local qw(timelocal_nocheck);
+use Business::CreditCard 0.35;
+use HTML::Entities;
+use Text::CSV_XS;
+use Spreadsheet::WriteExcel;
+use OLE::Storage_Lite;
+use FS::UI::Web::small_custview qw(small_custview); #less doh
+use FS::UI::Web;
+use FS::UI::bytecount qw( display_bytecount );
+use FS::Conf;
+#use FS::UID qw(dbh);
+use FS::Record qw(qsearch qsearchs dbh);
+use FS::Msgcat qw(gettext);
+use FS::Misc qw(card_types money_pretty);
+use FS::Misc::DateTime qw(parse_datetime);
+use FS::TicketSystem;
+use FS::ClientAPI_SessionCache;
+use FS::cust_svc;
+use FS::svc_acct;
+use FS::svc_forward;
+use FS::svc_domain;
+use FS::svc_phone;
+use FS::svc_external;
+use FS::svc_dsl;
+use FS::dsl_device;
+use FS::part_svc;
+use FS::cust_main;
+use FS::cust_bill;
+use FS::legacy_cust_bill;
+use FS::cust_main_county;
+use FS::part_pkg;
+use FS::cust_pkg;
+use FS::payby;
+use FS::acct_rt_transaction;
+use FS::msg_template;
+use FS::contact;
+use FS::cust_contact;
+use FS::cust_location;
+use FS::cust_payby;
+
+$DEBUG = 0;
+$me = '[FS::ClientAPI::PaymentOnly]';
+
+sub _cache {
+  $cache ||= new FS::ClientAPI_SessionCache( {
+               'namespace' => 'FS::ClientAPI::PaymentOnly',
+             } );
+}
+
+sub payment_only_skin_info {
+  my $p = shift;
+
+  my($context, $session, $custnum) = _custoragent_session_custnum($p);
+  #return { 'error' => $session } if $context eq 'error';
+
+  my $agentnum = '';
+  if ( $context eq 'customer' && $custnum ) {
+
+    my $sth = dbh->prepare('SELECT agentnum FROM cust_main WHERE custnum = ?')
+      or die dbh->errstr;
+
+    $sth->execute($custnum) or die $sth->errstr;
+
+    $agentnum = $sth->fetchrow_arrayref->[0]
+      or die "no agentnum for custnum $custnum";
+
+  #} elsif ( $context eq 'agent' ) {
+  } elsif ( defined($p->{'agentnum'}) and $p->{'agentnum'} =~ /^(\d+)$/ ) {
+    $agentnum = $1;
+  }
+  $p->{'agentnum'} = $agentnum;
+
+  my $conf = new FS::Conf;
+
+  #false laziness w/Signup.pm
+
+  my $skin_info_cache_agent = _cache->get("skin_info_cache_agent$agentnum");
+
+  if ( $skin_info_cache_agent ) {
+
+    warn "$me loading cached skin info for agentnum $agentnum\n"
+      if $DEBUG > 1;
+
+  } else {
+
+    warn "$me populating skin info cache for agentnum $agentnum\n"
+      if $DEBUG > 1;
+
+    $skin_info_cache_agent = {
+      'agentnum' => $agentnum,
+      ( map { $_ => scalar( $conf->config($_, $agentnum) ) }
+        qw( company_name date_format ) ),
+      ( map { $_ => scalar( $conf->config("selfservice-$_", $agentnum ) ) }
+        qw( body_bgcolor box_bgcolor stripe1_bgcolor stripe2_bgcolor
+            text_color link_color vlink_color hlink_color alink_color
+            font title_color title_align title_size menu_bgcolor menu_fontsize
+          )
+      ),
+      'menu_disable' => [ $conf->config('selfservice-menu_disable',$agentnum) ],
+      ( map { $_ => $conf->exists("selfservice-$_", $agentnum ) }
+        qw( menu_skipblanks menu_skipheadings menu_nounderline no_logo enable_payment_without_balance )
+      ),
+      ( map { $_ => scalar($conf->config_binary("selfservice-$_", $agentnum)) }
+        qw( title_left_image title_right_image
+            menu_top_image menu_body_image menu_bottom_image
+          )
+      ),
+      'logo' => scalar($conf->config_binary('logo.png', $agentnum )),
+      ( map { $_ => join("\n", $conf->config("selfservice-$_", $agentnum ) ) }
+        qw( head body_header body_footer company_address ) ),
+      'money_char' => $conf->config("money_char") || '$',
+      'menu' => 'payment_only_payment.php Make Payment
+
+                 payment_only_logout.php Logout
+                ',
+    };
+
+    _cache->set("skin_info_cache_agent$agentnum", $skin_info_cache_agent);
+
+  }
+
+  #{ %$skin_info_cache_agent };
+  $skin_info_cache_agent;
+
+}
+
+sub ip_login {
+  my $p = shift;
+
+  my $conf = new FS::Conf;
+
+  my $svc_x = '';
+  my $session = {};
+  my $cust_main;
+
+  return { error => 'MAC address empty '.$p->{'mac'} }
+    unless $p->{'mac'};
+
+      my $mac_address = $p->{'mac'};
+      $mac_address =~ s/[\:\,\-\. ]//g;
+      $mac_address =~ tr/[a-z]/[A-Z/;
+
+      my $svc_broadband = qsearchs( 'svc_broadband', { 'mac_addr' => $mac_address } );
+      return { error => 'MAC address not found $mac_address '.$p->{'mac'} }
+        unless $svc_broadband;
+      $svc_x = $svc_broadband;
+
+  if ( $svc_x ) {
+
+    $session->{'svcnum'} = $svc_x->svcnum;
+
+    my $cust_svc = $svc_x->cust_svc;
+    my $cust_pkg = $cust_svc->cust_pkg;
+    if ( $cust_pkg ) {
+      $cust_main = $cust_pkg->cust_main;
+      $session->{'custnum'} = $cust_main->custnum;
+      if ( $conf->exists('pkg-balances') ) {
+        my @cust_pkg = grep { $_->part_pkg->freq !~ /^(0|$)/ }
+                            $cust_main->ncancelled_pkgs;
+        $session->{'pkgnum'} = $cust_pkg->pkgnum
+          if scalar(@cust_pkg) > 1;
+      }
+    }
+
+    #my $pkg_svc = $svc_acct->cust_svc->pkg_svc;
+    #return { error => 'Only primary user may log in.' } 
+    #  if $conf->exists('selfservice_server-primary_only')
+    #    && ( ! $pkg_svc || $pkg_svc->primary_svc ne 'Y' );
+    my $part_pkg = $cust_pkg->part_pkg;
+    return { error => 'Only primary user may log in.' }
+      if $conf->exists('selfservice_server-primary_only')
+         && $cust_svc->svcpart != $part_pkg->svcpart([qw( svc_acct svc_phone )]);
+
+  }
+  else {
+    return { error => "No Service Found with Mac Address ".$p->{'mac'} };
+  }
+
+  ## get account information
+  my ($cust_payby_card) = $cust_main->cust_payby('CARD', 'DCRD');
+  if ($cust_payby_card) {
+    $session->{'CARD'} = $cust_payby_card->custpaybynum;
+  }
+  my ($cust_payby_check) = $cust_main->cust_payby('CHEK', 'DCHK');
+  if ($cust_payby_check) {
+    $session->{'CHEK'} = $cust_payby_check->custpaybynum;
+  }
+
+  my $session_id;
+  do {
+    $session_id = sha512_hex(time(). {}. rand(). $$)
+  } until ( ! defined _cache->get($session_id) ); #just in case
+
+  my $timeout = $conf->config('selfservice-session_timeout') || '1 hour';
+  _cache->set( $session_id, $session, $timeout );
+
+  return { 'error'      => '',
+           'session_id' => $session_id,
+           %$session,
+         };
+}
+
+sub ip_logout {
+  my $p = shift;
+  my $skin_info = skin_info($p);
+  if ( $p->{'session_id'} ) {
+    _cache->remove($p->{'session_id'});
+    return { %$skin_info, 'error' => '' };
+  } else {
+    return { %$skin_info, 'error' => "Can't resume session" }; #better error message
+  }
+}
+
+sub get_mac_address {
+  my $p = shift;
+
+## access radius exports acct tables to get mac
+  my @part_export = ();
+  @part_export = (
+    qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ),
+    qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } ),
+    qsearch( 'part_export', { 'exporttype' => 'broadband_sqlradius' } ),
+  );
+
+  my @sessions;
+  foreach my $part_export (@part_export) {
+    push @sessions, ( @{ $part_export->usage_sessions( {
+      'ip' => $p->{'ip'},
+      'session_status' => 'open',
+    } ) } );
+  }
+
+  return { 'mac_address' => $sessions[0]->{'callingstationid'}, };
+}
+
+sub payment_only_payment_info {
+  my $p = shift;
+  my $session = _cache->get($p->{'session_id'})
+    or return { 'error' => "Can't resume session today" }; #better error message
+
+  my $custnum = $session->{'custnum'};
+  my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+    or return { 'error' => "unknown custnum $custnum" };
+
+  my $payment_info = {
+    'balance' => $cust_main->balance,
+  };
+
+  #doubleclick protection
+  my $_date = time;
+  $payment_info->{'payunique'} = "webui-PaymentOnly-$_date-$$-". rand() * 2**32; #new
+  $payment_info->{'paybatch'} = $payment_info->{'payunique'};  #back compat
+
+  if ($session->{'CARD'}) {
+    my $card_payby = qsearchs('cust_payby', { 'custpaybynum' => $session->{'CARD'} });
+    if ($card_payby) {
+       $payment_info->{'CARD'} = $session->{'CARD'};
+       $payment_info->{'card_mask'} = $card_payby->paymask;
+       $payment_info->{'card_type'} = $card_payby->paycardtype;
+    }
+  }
+
+  if ($session->{'CHEK'}) {
+    my $check_payby = qsearchs('cust_payby', { 'custpaybynum' => $session->{'CHEK'} });
+    if ($check_payby) {
+       my ($payaccount, $payaba) = split /\@/, $check_payby->paymask;
+       $payment_info->{'CHEK'} = $session->{'CHEK'};
+       $payment_info->{'check_mask'} = $payaccount;
+       $payment_info->{'check_type'} = $check_payby->paytype;
+    }
+  }
+
+  return $payment_info;
+
+}
+
+sub payment_only_process_payment {
+  my $p = shift;
+
+  my $payment_info = _validate_payment($p);
+  return $payment_info if $payment_info->{'error'};
+
+  FS::ClientAPI::MyAccount::do_process_payment($payment_info);
+
+  #return;
+}
+
+sub _validate_payment {
+  my $p = shift;
+
+  my $session = _cache->get($p->{'session_id'})
+    or return { 'error' => "Can't resume session" }; #better error message
+
+  my $custnum = $session->{'custnum'};
+
+  my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+    or return { 'error' => "unknown custnum $custnum" };
+
+  $p->{'amount'} =~ /^\s*(\d+(\.\d{2})?)\s*$/
+    or return { 'error' => gettext('illegal_amount') };
+  my $amount = $1;
+  return { error => 'Amount must be greater than 0' } unless $amount > 0;
+
+  #false laziness w/tr-amount_fee.html, but we don't want selfservice users
+  #changing the hidden form values
+  my $conf = new FS::Conf;
+  my $fee_display = $conf->config('selfservice_process-display') || 'add';
+  my $fee_pkgpart = $conf->config('selfservice_process-pkgpart', $cust_main->agentnum);
+  my $fee_skip_first = $conf->exists('selfservice_process-skip_first');
+  if ( $fee_display eq 'add'
+         and $fee_pkgpart
+         and ! $fee_skip_first || scalar($cust_main->cust_pay)
+     )
+  {
+    my $fee_pkg = qsearchs('part_pkg', { pkgpart=>$fee_pkgpart } );
+    $amount = sprintf('%.2f', $amount + $fee_pkg->option('setup_fee') );
+  }
+
+  #$p->{'payby'} ||= 'CARD';
+  $p->{'payby'} =~ /^([A-Z]{4})$/
+    or return { 'error' => "illegal_payby " . $p->{'payby'} };
+  my $payby = $1;
+
+  ## get info from custpaybynum.
+  my $custpayby = qsearchs('cust_payby', { custpaybynum => $session->{$p->{'payby'}} } )
+    or return { 'error' => 'No payment information found' };
+
+  $p->{'discount_term'} =~ /^\s*(\d*)\s*$/
+    or return { 'error' => gettext('illegal_discount_term'). ': '. $p->{'discount_term'} };
+  my $discount_term = $1;
+
+  $p->{'payunique'} =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
+    or return { 'error' => gettext('illegal_text'). " payunique: ". $p->{'payunique'} };
+  my $payunique = $1;
+
+  $p->{'paybatch'} =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
+    or return { 'error' => gettext('illegal_text'). " paybatch: ". $p->{'paybatch'} };
+  my $paybatch = $1;
+
+  $payunique = $paybatch if ! length($payunique) && length($paybatch);
+  my $payname = $custpayby->payname;
+
+  #false laziness w/process/payment.cgi
+  my $payinfo = $custpayby->payinfo;
+  my $onfile = 1;
+  my $paycvv = '';
+  my $replace_cust_payby;
+
+  if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) {
+
+    my ($payinfo1, $payinfo2) = split /\@/, $payinfo;
+    $payinfo1 =~ /^([\dx]+)$/
+      or return { 'error' => "illegal account number " };
+     $payinfo2 =~ /^([\dx]+)$/
+      or return { 'error' => "illegal ABA/routing number " }; 
+
+  } elsif ( $payby eq 'CARD' || $payby eq 'DCRD' ) {
+
+    $payinfo =~ s/\D//g;
+    $payinfo =~ /^(\d{13,19}|\d{8,9})$/
+      or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
+    $payinfo = $1;
+
+    validate($payinfo)
+      or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
+    return { 'error' => gettext('unknown_card_type') }
+      if !$cust_main->tokenized($payinfo) && cardtype($payinfo) eq "Unknown";
+
+    if ( length($p->{'paycvv'}) && $p->{'paycvv'} !~ /^\s*$/ ) {
+      if ( cardtype($payinfo) eq 'American Express card' ) {
+        $p->{'paycvv'} =~ /^\s*(\d{4})\s*$/
+          or return { 'error' => "CVV2 (CID) for American Express cards is four digits." };
+        $paycvv = $1;
+      } else {
+        $p->{'paycvv'} =~ /^\s*(\d{3})\s*$/
+          or return { 'error' => "CVV2 (CVC2/CID) is three digits." };
+        $paycvv = $1;
+      }
+    } elsif ( $conf->exists('selfservice-onfile_require_cvv') ) {
+      return { 'error' => 'CVV2 is required' };
+    } elsif ( !$onfile && $conf->exists('selfservice-require_cvv') ) {
+      return { 'error' => 'CVV2 is required' };
+    }
+  
+  } else {
+    die "unknown payby $payby";
+  }
+
+  $p->{$_} = $cust_main->bill_location->get($_) 
+    for qw(address1 address2 city state zip);
+
+  my %payby2fields = (
+    'CARD' => [ qw( paystart_month paystart_year payissue payip
+                    address1 address2 city state zip country    ) ],
+    'CHEK' => [ qw( ss paytype paystate stateid stateid_state payip ) ],
+  );
+
+  my $card_type = '';
+  $card_type = cardtype($payinfo) if $payby eq 'CARD';
+
+  my ($year, $month, $day) = split /-/, $custpayby->{Hash}->{paydate};
+
+  my $return = { 
+    'cust_main'      => $cust_main, #XXX or just custnum??
+    'amount'         => sprintf('%.2f', $amount),
+    'payby'          => $payby,
+    'payinfo'        => $payinfo,
+    'paymask'        => $custpayby->paymask,
+    'card_type'      => $card_type,
+    'paydate'        => $custpayby->paydate,
+    'paydate_pretty' => $month. ' / '. $year,
+    'month'          => $month,
+    'year'           => $year,
+    'payname'        => $custpayby->{HASH}->{payname},
+    'payunique'      => $payunique,
+    'paybatch'       => $paybatch,
+    'paycvv'         => $paycvv,
+    'payname'        => $payname,
+    'discount_term'  => $discount_term,
+    'pkgnum'         => $session->{'pkgnum'},
+    map { $_ => $p->{$_} } ( @{ $payby2fields{$payby} } )
+  };
+
+  return $return;
+
+}
+
+sub _custoragent_session_custnum {
+  my $p = shift;
+
+  my($context, $session, $custnum);
+  if ( $p->{'session_id'} ) {
+
+    $context = 'customer';
+    $session = _cache->get($p->{'session_id'})
+      or return ( 'error' => "Can't resume session" ); #better error message
+    $custnum = $session->{'custnum'};
+
+  } elsif ( $p->{'agent_session_id'} ) {
+
+    $context = 'agent';
+    my $agent_cache = new FS::ClientAPI_SessionCache( {
+      'namespace' => 'FS::ClientAPI::Agent',
+    } );
+    $session = $agent_cache->get($p->{'agent_session_id'})
+      or return ( 'error' => "Can't resume session" ); #better error message
+    $custnum = $p->{'custnum'};
+
+  } else {
+    $context = 'error';
+    return ( 'error' => "Can't resume session" ); #better error message
+  }
+
+  ($context, $session, $custnum);
+
+}
+
+1;
\ No newline at end of file
diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm
index dcf34fdaa..430edfe36 100644
--- a/FS/FS/ClientAPI_XMLRPC.pm
+++ b/FS/FS/ClientAPI_XMLRPC.pm
@@ -50,6 +50,7 @@ our %typefix = (
   'invoice_pdf'          => { 'invoice_pdf' => 'base64', },
   'legacy_invoice_pdf'   => { 'invoice_pdf' => 'base64', },
   'skin_info'            => \%typefix_skin_info,
+  'payment_only_skin_info' => \%typefix_skin_info,
   'login_info'           => \%typefix_skin_info,
   'logout'               => \%typefix_skin_info,
   'access_info'          => \%typefix_skin_info,
@@ -215,6 +216,7 @@ sub ss2clientapi {
   'suspend_username'          => 'Agent/suspend_username',
   'unsuspend_username'        => 'Agent/unsuspend_username',
   'mason_comp'                => 'MasonComponent/mason_comp',
+  'payment_only_mason_comp'   => 'MasonComponent/payment_only_mason_comp',
   'call_time'                 => 'PrepaidPhone/call_time',
   'call_time_nanpa'           => 'PrepaidPhone/call_time_nanpa',
   'phonenum_balance'          => 'PrepaidPhone/phonenum_balance',
@@ -227,6 +229,12 @@ sub ss2clientapi {
   'quotation_add_pkg'         => 'MyAccount/quotation/quotation_add_pkg',
   'quotation_remove_pkg'      => 'MyAccount/quotation/quotation_remove_pkg',
   'quotation_order'           => 'MyAccount/quotation/quotation_order',
+  'ip_login'                  => 'PaymentOnly/ip_login',
+  'ip_logout'                 => 'PaymentOnly/ip_logout',
+  'get_mac_address'           => 'PaymentOnly/get_mac_address',
+  'payment_only_skin_info'    => 'PaymentOnly/payment_only_skin_info',
+  'payment_only_payment_info' => 'PaymentOnly/payment_only_payment_info',
+  'payment_only_process_payment' => 'PaymentOnly/payment_only_process_payment',
 
   'freesideinc_service'       => 'Freeside/freesideinc_service',
   };
diff --git a/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm b/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm
index 506dce1de..58ce6a801 100644
--- a/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm
+++ b/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm
@@ -45,6 +45,7 @@ our %typefix = (
   'invoice_pdf'          => { 'invoice_pdf' => 'base64', },
   'legacy_invoice_pdf'   => { 'invoice_pdf' => 'base64', },
   'skin_info'            => \%typefix_skin_info,
+  'payment_only_skin_info' => \%typefix_skin_info,
   'login_info'           => \%typefix_skin_info,
   'logout'               => \%typefix_skin_info,
   'access_info'          => \%typefix_skin_info,
diff --git a/fs_selfservice/FS-SelfService/cgi/change_pay.html b/fs_selfservice/FS-SelfService/cgi/change_pay.html
index f90f6d92b..bd64907e2 100644
--- a/fs_selfservice/FS-SelfService/cgi/change_pay.html
+++ b/fs_selfservice/FS-SelfService/cgi/change_pay.html
@@ -68,7 +68,7 @@
     selected_layer => $payby,
 #    form_name => 'dummy',
 #    form_action => 'dummy.cgi',
-    layer_callback => sub { my $layer = shift; return '<TABLE BGCOLOR="#cccccc">'.$paybychecked{$layer}.qq!<INPUT TYPE="hidden" NAME="payby" VALUE="$layer">$tail!; },
+    layer_callback => sub { my $layer = shift; use MyLog; use Data::Dumper; MyLog->mylog("my layer $layer\n"); return '<TABLE BGCOLOR="#cccccc">'.$paybychecked{$layer}.qq!<INPUT TYPE="hidden" NAME="payby" VALUE="$layer">$tail!; },
   )->html;
 
 %>
diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi
index 3dc69e142..999b812d9 100755
--- a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi
+++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi
@@ -284,7 +284,11 @@ sub change_bill {
   };
 }
 sub change_ship { change_bill(@_); }
-sub change_pay { change_bill(@_); }
+sub change_pay {
+  my @payby = ('CARD', 'CHEK', 'DCHK');
+  use MyLog; use Data::Dumper; MyLog->mylog("my change pay at\n".Dumper(@_));
+  change_bill(@_);
+}
 
 sub change_creditcard_pay { change_bill('CARD'); }
 sub change_check_pay { change_bill('CHEK'); }
diff --git a/ng_selfservice/elements/payment_only_menu.php b/ng_selfservice/elements/payment_only_menu.php
new file mode 100644
index 000000000..8fb9331e3
--- /dev/null
+++ b/ng_selfservice/elements/payment_only_menu.php
@@ -0,0 +1,101 @@
+<?
+
+require_once('session.php');
+
+$skin_info = $freeside->payment_only_skin_info( array(
+  'session_id' => $_COOKIE['session_id'],
+) );
+
+
+if ( isset($skin_info['error']) && $skin_info['error'] ) {
+  $error = $skin_info['error'];
+  header('Location:payment_only_error.php?error='. urlencode($error));
+  die();
+}
+
+extract($skin_info);
+
+?>
+<style type="text/css">
+#menu_ul ul li {
+	display: inline;
+	width: 100%;
+} 
+</style>
+
+<ul id="menu_ul">
+
+<?
+
+  $menu_array = explode("\n", $menu);
+  $submenu = array();
+
+  foreach ($menu_array AS $menu_item) {
+    if ( preg_match('/^\s*$/', $menu_item) ) {
+      print_menu($submenu, $current_menu, $menu_disable);
+      $submenu = array();
+    } else {
+      $submenu[] = $menu_item;
+    }
+  }
+  print_menu($submenu, $current_menu, $menu_disable);
+
+  function print_menu($submenu_array, $current_menu, $menu_disable) {
+    if ( count($submenu_array) == 0 ) { return; }
+
+    $links = array();
+    $labels = array();
+    foreach ($submenu_array AS $submenu_item) {
+      $pieces = preg_split('/\s+/', $submenu_item, 2, PREG_SPLIT_NO_EMPTY);
+      $links[] = $pieces[0];
+      $labels[] = $pieces[1];
+    }
+
+    print_link($links[0], $labels[0], $current_menu, $links);
+
+    if ( count($links) > 1 ) {
+      if ( in_array( $current_menu, $links ) ) {
+        echo '<img src="images/dropdown_arrow_white.gif">';
+      } else {
+        echo '<img src="images/dropdown_arrow_white.gif" style="display:none;">';
+        echo '<img src="images/dropdown_arrow_grey.gif">';
+      }
+    }
+
+    array_shift($links);
+    array_shift($labels);
+
+    echo '</a>';
+
+    if ( count($links) > 0 ) {
+      echo '<ul>';
+      foreach ($links AS $link) {
+        $label = array_shift($labels);
+        if ( in_array($label, $menu_disable) == 0) {
+          print_link($link, $label, $current_menu, array($link) );
+          echo '</a></li>';
+        }
+      }
+      echo '</ul>';
+    }
+
+    echo '</li>';
+
+  }
+
+  function print_link($link, $label, $current_menu, $search_array) {
+      echo '<li><a href="'. $link. '"';
+      if ( in_array( $current_menu, $search_array ) ) {
+        echo ' class="current_menu"';
+      }
+      echo '>'. _($label);
+  }
+
+?>
+
+</ul>
+
+<div style="clear:both;"></div>
+<table cellpadding="0" cellspacing="0" border="0" style="min-width:666px">
+<tr>
+<td class="page">
\ No newline at end of file
diff --git a/ng_selfservice/payment_only.php b/ng_selfservice/payment_only.php
new file mode 100644
index 000000000..d348b9cbc
--- /dev/null
+++ b/ng_selfservice/payment_only.php
@@ -0,0 +1,52 @@
+<?
+
+require('freeside.class.php');
+$freeside = new FreesideSelfService();
+
+$ip = $_SERVER['REMOTE_ADDR'];
+
+$mac = $freeside->get_mac_address( array('ip' => $ip, ) );
+
+$response = $freeside->ip_login( array( 
+  'mac' => $mac['mac_address'],
+) );
+
+$error = $response['error'];
+
+if ( $error ) {
+
+  $title ='Login'; include('elements/header.php');
+  include('elements/error.php');	
+  echo "Sorry "+$error;
+
+ // header('Location:index.php?username='. urlencode($mac).
+ //                          '&domain='.   urlencode($domain).
+ //                          '&email='.    urlencode($email).
+ //                          '&error='.    urlencode($error)
+ //       );
+
+}
+else {
+  // sucessful login
+
+  $session_id = $response['session_id'];
+  $mac = $mac['mac_address'];
+
+  error_log("[login] logged into freeside with ip=$ip and mac=$mac, setting cookie");
+
+  setcookie('session_id', $session_id);
+
+  $title ='IP Login';
+
+  if ( $response['custnum'] || $response['svcnum'] ) {
+
+    header("Location:payment_only_payment.php");
+    die();
+
+  }   
+
+} //successfull login
+
+?>
+
+<? include('elements/footer.php'); ?>
\ No newline at end of file
diff --git a/ng_selfservice/payment_only_error.php b/ng_selfservice/payment_only_error.php
new file mode 100644
index 000000000..2ee9f53fa
--- /dev/null
+++ b/ng_selfservice/payment_only_error.php
@@ -0,0 +1,6 @@
+<? $error = $_GET['error']; ?>
+<? $title ='Payment Only Error'; include('elements/header.php'); ?>
+There was in issue processing your payment.
+<P>
+<? include('elements/error.php'); ?>
+<? include('elements/footer.php'); ?>
\ No newline at end of file
diff --git a/ng_selfservice/payment_only_logout.php b/ng_selfservice/payment_only_logout.php
new file mode 100644
index 000000000..e172680cb
--- /dev/null
+++ b/ng_selfservice/payment_only_logout.php
@@ -0,0 +1,35 @@
+<?
+
+require('freeside.class.php');
+$freeside = new FreesideSelfService();
+
+$response = $freeside->ip_logout( array(
+  'session_id' => $_COOKIE['session_id'],
+) );
+
+setcookie('session_id', '', time() - 3600);
+
+$error = $response['error'];
+
+if ( $error ) {
+  error_log("Logout error: $error ");
+}
+
+?>
+
+<!DOCTYPE html>
+<HTML>
+  <HEAD>
+    <TITLE>
+      Logged Out
+    </TITLE>
+    <link href="css/default.css" rel="stylesheet" type="text/css"/>
+    <script type="text/javascript" src="js/jquery.js"></script>
+    <script type="text/javascript" src="js/menu.js"></script>
+  </HEAD>
+  <BODY>
+    <FONT SIZE=5>Logged Out</FONT>
+    <BR><BR>
+    You have been logged out.  
+  </BODY>
+</HTML>
\ No newline at end of file
diff --git a/ng_selfservice/payment_only_payment.php b/ng_selfservice/payment_only_payment.php
new file mode 100644
index 000000000..baed422d1
--- /dev/null
+++ b/ng_selfservice/payment_only_payment.php
@@ -0,0 +1,118 @@
+<? $title ='Make A Payment'; include('elements/header.php'); ?>
+<? $current_menu = 'payment_only_payment.php'; include('elements/payment_only_menu.php'); ?>
+
+<?
+
+if ( isset($_POST['amount']) && $_POST['amount'] ) {
+
+  $payment_results = $freeside->payment_only_process_payment(array(
+    'session_id' => $_COOKIE['session_id'],
+    'payby'      => $_POST['payby'],
+    'amount'     => $_POST['amount'],
+    'paybatch'   => $_POST['paybatch'],
+    //'discount_term' => $discount_term,
+  ));
+
+  if ( $payment_results['error'] ) {
+    $error = $payment_results['error'];
+  } else {
+    $receipt_html = $payment_results['receipt_html'];
+  }
+
+}
+
+#echo print_r($payment_results);
+
+if ( $receipt_html ) { 
+?>
+
+  Your payment was processed successfully.  Thank you.<BR><BR>
+  <? echo $receipt_html; ?>
+
+<? } else {
+
+  $payment_info = $freeside->payment_only_payment_info( array(
+    'session_id' => $_COOKIE['session_id'],
+  ) );
+
+  if ( isset($payment_info['error']) && $payment_info['error'] ) {
+    $error = $payment_info['error'];
+    // possible to just keep on this page
+    header('Location:payment_only_error.php?error='. urlencode($error));
+    die();
+  }
+
+  extract($payment_info);
+
+  $tr_amount_fee = $freeside->payment_only_mason_comp(array(
+      'session_id' => $_COOKIE['session_id'],
+      'comp'       => '/elements/tr-amount_fee.html',
+      'args'       => [ 'amount',  $balance ],
+  ));
+  //$tr_amount_fee = $tr_amount_fee->{'error'} || $tr_amount_fee->{'output'};
+  $tr_amount_fee = $tr_amount_fee['output'];
+
+ ?>
+
+  <? include('elements/error.php'); ?>
+
+  <SCRIPT TYPE="text/javascript">
+
+      function payby_changed(what) {
+        var amount = document.getElementById('amount');
+        var amountdue = document.getElementById('amountdue');
+        var surcharge_cell = document.getElementById('ajax_surcharge_cell');
+        var surcharge_percentage = document.getElementById('surcharge_percentage');
+        var surcharge_flatfee = document.getElementById('surcharge_flatfee');
+        if (what.value == "CHEK") {
+          surcharge_cell.style.display = 'none';
+          amount.value = amountdue.value;
+
+        }
+        else if (what.value == "CARD") {
+        	surcharge_cell.style.display = 'inline';
+        	amount.value = (+amountdue.value + (+amountdue.value * +surcharge_percentage.value) + +surcharge_flatfee.value).toFixed(2);
+        }
+      }
+
+    </SCRIPT>
+
+  <FORM NAME="OneTrueForm" METHOD="POST" ACTION="payment_only_payment.php" onSubmit="document.OneTrueForm.process.disabled=true">
+
+  <TABLE>
+
+  <TR>
+  	<TD ALIGN="right"><B>Payment account</B></TD>
+  	<TD COLSPAN=7>
+  	  <SELECT ID="payby" NAME="payby" onChange="payby_changed(this)">
+<? if ($CARD) { ?>
+         <OPTION VALUE="CARD"><? echo $card_type ?> <? echo $card_mask ?></OPTION>
+<? } ?>
+<? if ($CHEK) { ?>
+         <OPTION VALUE="CHEK"><? echo $check_type ?> <? echo $check_mask ?></OPTION>
+<? } ?> 	  	
+  	  </SELECT>
+  	</TD>
+  </TR>
+  	
+  <TR>
+    <TD ALIGN="right"><B>Amount Due</B></TD>
+    <TD COLSPAN=7>
+      <TABLE><TR><TD>
+        $<? echo sprintf("%.2f", $balance) ?>
+        <INPUT TYPE=hidden NAME="amountdue" ID="amountdue" VALUE="<? echo sprintf("%.2f", $balance) ?>" >
+      </TD></TR></TABLE>
+    </TD>
+  </TR>
+
+  <? echo $tr_amount_fee; ?>
+
+  </TABLE>
+  <BR>
+  <INPUT TYPE="hidden" NAME="paybatch" VALUE="<? echo $paybatch ?>">
+  <INPUT TYPE="submit" NAME="process" VALUE="Process payment"> <!-- onClick="this.disabled=true"> -->
+  </FORM>
+
+<? } ?>
+<? include('elements/menu_footer.php'); ?>
+<? include('elements/footer.php'); ?>
\ No newline at end of file

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

Summary of changes:
 FS/FS/ClientAPI/MasonComponent.pm                  |  29 +-
 FS/FS/ClientAPI/PaymentOnly.pm                     | 472 +++++++++++++++++++++
 FS/FS/ClientAPI_XMLRPC.pm                          |   8 +
 .../FS-SelfService/SelfService/XMLRPC.pm           |   1 +
 fs_selfservice/FS-SelfService/cgi/change_pay.html  |   2 +-
 fs_selfservice/FS-SelfService/cgi/selfservice.cgi  |   6 +-
 .../elements/{menu.php => payment_only_menu.php}   |   6 +-
 ng_selfservice/payment_only.php                    |  52 +++
 ng_selfservice/payment_only_error.php              |   6 +
 ng_selfservice/payment_only_logout.php             |  35 ++
 ng_selfservice/payment_only_payment.php            | 118 ++++++
 11 files changed, 726 insertions(+), 9 deletions(-)
 create mode 100644 FS/FS/ClientAPI/PaymentOnly.pm
 copy ng_selfservice/elements/{menu.php => payment_only_menu.php} (93%)
 create mode 100644 ng_selfservice/payment_only.php
 create mode 100644 ng_selfservice/payment_only_error.php
 create mode 100644 ng_selfservice/payment_only_logout.php
 create mode 100644 ng_selfservice/payment_only_payment.php




More information about the freeside-commits mailing list