changes to support PayPal, #22395
authorMark Wells <mark@freeside.biz>
Thu, 25 Apr 2013 21:45:49 +0000 (14:45 -0700)
committerMark Wells <mark@freeside.biz>
Thu, 25 Apr 2013 21:46:10 +0000 (14:46 -0700)
17 files changed:
FS/FS/ClientAPI/Signup.pm
FS/FS/Conf.pm
FS/FS/Schema.pm
FS/FS/agent.pm
FS/FS/cust_main.pm
FS/FS/cust_main/Billing_Realtime.pm
FS/FS/payby.pm
FS/FS/payinfo_Mixin.pm
FS/FS/payment_gateway.pm
fs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html
fs_selfservice/FS-SelfService/cgi/myaccount.html
fs_selfservice/FS-SelfService/cgi/myaccount_menu.html
fs_selfservice/FS-SelfService/cgi/selfservice.cgi
fs_selfservice/FS-SelfService/cgi/verify.cgi
httemplate/edit/agent_payment_gateway.html
httemplate/edit/payment_gateway.html
httemplate/edit/process/payment_gateway.html

index 1dbb20b..c3beb69 100644 (file)
@@ -946,15 +946,27 @@ sub capture_payment {
   }
 
   my $cust_main = $cust_pay_pending->cust_main;
   }
 
   my $cust_main = $cust_pay_pending->cust_main;
-  my $bill_error =
-    $cust_main->realtime_botpp_capture( $cust_pay_pending, 
-      %{$packet->{data}},
-      apply => 1,
-  );
+  if ( $packet->{cancel} ) {
+    # the user has chosen not to make this payment
+    # (probably should be a separate API call, but I don't want to duplicate
+    # all of the above...which should eventually go away)
+    my $error = $cust_pay_pending->delete;
+    # don't show any errors related to this; they're not meaningful
+    warn "error canceling pending payment $paypendingnum: $error\n" if $error;
+    return { 'error'      => '_cancel',
+             'session_id' => $cust_pay_pending->session_id };
+  } else {
+    # create the payment
+    my $bill_error =
+      $cust_main->realtime_botpp_capture( $cust_pay_pending, 
+        %{$packet->{data}},
+        apply => 1,
+    );
 
 
-  return { 'error'      => ( $bill_error->{bill_error} ? '_decline' : '' ),
-           %$bill_error,
-         };
+    return { 'error'      => ( $bill_error->{bill_error} ? '_decline' : '' ),
+             %$bill_error,
+           };
+  }
 
 }
 
 
 }
 
index 6a19ff4..8dbc070 100644 (file)
@@ -2098,7 +2098,7 @@ and customer address. Include units.',
     'section'     => 'self-service',
     'description' => 'Acceptable payment types for the signup server',
     'type'        => 'selectmultiple',
     'section'     => 'self-service',
     'description' => 'Acceptable payment types for the signup server',
     'type'        => 'selectmultiple',
-    'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB PREPAY BILL COMP) ],
+    'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB PREPAY PPAL BILL COMP) ],
   },
 
   {
   },
 
   {
@@ -2472,7 +2472,7 @@ and customer address. Include units.',
     'section'     => 'billing',
     'description' => 'Available payment types.',
     'type'        => 'selectmultiple',
     'section'     => 'billing',
     'description' => 'Available payment types.',
     'type'        => 'selectmultiple',
-    'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD COMP) ],
+    'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD PPAL COMP) ],
   },
 
   {
   },
 
   {
@@ -2480,7 +2480,7 @@ and customer address. Include units.',
     'section'     => 'UI',
     'description' => 'Default payment type.  HIDE disables display of billing information and sets customers to BILL.',
     'type'        => 'select',
     'section'     => 'UI',
     'description' => 'Default payment type.  HIDE disables display of billing information and sets customers to BILL.',
     'type'        => 'select',
-    'select_enum' => [ '', qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD COMP HIDE) ],
+    'select_enum' => [ '', qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD PPAL COMP HIDE) ],
   },
 
   {
   },
 
   {
index eb73ccb..4f395f2 100644 (file)
@@ -3267,7 +3267,8 @@ sub tables_hashref {
         'gateway_username', 'varchar',  'NULL', $char_d, '', '', 
         'gateway_password', 'varchar',  'NULL', $char_d, '', '', 
         'gateway_action',   'varchar',  'NULL', $char_d, '', '', 
         'gateway_username', 'varchar',  'NULL', $char_d, '', '', 
         'gateway_password', 'varchar',  'NULL', $char_d, '', '', 
         'gateway_action',   'varchar',  'NULL', $char_d, '', '', 
-        'gateway_callback_url', 'varchar',  'NULL', $char_d, '', '', 
+        'gateway_callback_url', 'varchar',  'NULL', 255, '', '', 
+        'gateway_cancel_url',   'varchar',  'NULL', 255, '', '',
         'disabled',   'char',  'NULL',   1, '', '', 
       ],
       'primary_key' => 'gatewaynum',
         'disabled',   'char',  'NULL',   1, '', '', 
       ],
       'primary_key' => 'gatewaynum',
index 3794d3f..9b32209 100644 (file)
@@ -216,7 +216,7 @@ an attempt will be made to select a gateway suited for the taxes paid on
 the invoice.
 
 The I<method> and I<payinfo> options can be used to influence the choice
 the invoice.
 
 The I<method> and I<payinfo> options can be used to influence the choice
-as well.  Presently only 'CC' and 'ECHECK' methods are meaningful.
+as well.  Presently only 'CC', 'ECHECK', and 'PAYPAL' methods are meaningful.
 
 When the I<method> is 'CC' then the card number in I<payinfo> can direct
 this routine to route to a gateway suited for that type of card.
 
 When the I<method> is 'CC' then the card number in I<payinfo> can direct
 this routine to route to a gateway suited for that type of card.
@@ -246,13 +246,17 @@ sub payment_gateway {
   }
 
   #look for an agent gateway override first
   }
 
   #look for an agent gateway override first
-  my $cardtype;
-  if ( $options{method} && $options{method} eq 'CC' && $options{payinfo} ) {
-    $cardtype = cardtype($options{payinfo});
-  } elsif ( $options{method} && $options{method} eq 'ECHECK' ) {
-    $cardtype = 'ACH';
-  } else {
-    $cardtype = $options{method} || '';
+  my $cardtype = '';
+  if ( $options{method} ) {
+    if ( $options{method} eq 'CC' && $options{payinfo} ) {
+      $cardtype = cardtype($options{payinfo});
+    } elsif ( $options{method} eq 'ECHECK' ) {
+      $cardtype = 'ACH';
+    } elsif ( $options{method} eq 'PAYPAL' ) {
+      $cardtype = 'PayPal';
+    } else {
+      $cardtype = $options{method}
+    }
   }
 
   my $override =
   }
 
   my $override =
index 2a4602e..1cf0365 100644 (file)
@@ -390,7 +390,7 @@ sub insert {
 
     $payby = 'PREP' if $amount;
 
 
     $payby = 'PREP' if $amount;
 
-  } elsif ( $self->payby =~ /^(CASH|WEST|MCRD)$/ ) {
+  } elsif ( $self->payby =~ /^(CASH|WEST|MCRD|PPAL)$/ ) {
 
     $payby = $1;
     $self->payby('BILL');
 
     $payby = $1;
     $self->payby('BILL');
@@ -2021,7 +2021,8 @@ sub check {
 
   if ( $self->paydate eq '' || $self->paydate eq '-' ) {
     return "Expiration date required"
 
   if ( $self->paydate eq '' || $self->paydate eq '-' ) {
     return "Expiration date required"
-      unless $self->payby =~ /^(BILL|PREPAY|CHEK|DCHK|LECB|CASH|WEST|MCRD)$/;
+      # shouldn't payinfo_check do this?
+      unless $self->payby =~ /^(BILL|PREPAY|CHEK|DCHK|LECB|CASH|WEST|MCRD|PPAL)$/;
     $self->paydate('');
   } else {
     my( $m, $y );
     $self->paydate('');
   } else {
     my( $m, $y );
index 804969b..1caa3e5 100644 (file)
@@ -111,7 +111,7 @@ L<http://420.am/business-onlinepayment> for supported gateways.
 
 Required arguments in the hashref are I<method>, and I<amount>
 
 
 Required arguments in the hashref are I<method>, and I<amount>
 
-Available methods are: I<CC>, I<ECHECK> and I<LEC>
+Available methods are: I<CC>, I<ECHECK>, I<LEC>, and I<PAYPAL>
 
 Available optional arguments are: I<description>, I<invnum>, I<apply>, I<quiet>, I<paynum_ref>, I<payunique>, I<session_id>
 
 
 Available optional arguments are: I<description>, I<invnum>, I<apply>, I<quiet>, I<paynum_ref>, I<payunique>, I<session_id>
 
@@ -317,6 +317,7 @@ my %bop_method2payby = (
   'CC'     => 'CARD',
   'ECHECK' => 'CHEK',
   'LEC'    => 'LECB',
   'CC'     => 'CARD',
   'ECHECK' => 'CHEK',
   'LEC'    => 'LECB',
+  'PAYPAL' => 'PPAL',
 );
 
 sub realtime_bop {
 );
 
 sub realtime_bop {
@@ -612,6 +613,7 @@ sub realtime_bop {
     %$bop_content,
     'reference'      => $cust_pay_pending->paypendingnum, #for now
     'callback_url'   => $payment_gateway->gateway_callback_url,
     %$bop_content,
     'reference'      => $cust_pay_pending->paypendingnum, #for now
     'callback_url'   => $payment_gateway->gateway_callback_url,
+    'cancel_url'     => $payment_gateway->gateway_cancel_url,
     'email'          => $email,
     %content, #after
   );
     'email'          => $email,
     %content, #after
   );
index d1961a5..e223a05 100644 (file)
@@ -208,6 +208,7 @@ sub longname {
   'CARD' => 'CC',
   'CHEK' => 'ECHECK',
   'MCRD' => 'CC',
   'CARD' => 'CC',
   'CHEK' => 'ECHECK',
   'MCRD' => 'CC',
+  'PPAL' => 'PAYPAL',
 );
 
 sub payby2bop {
 );
 
 sub payby2bop {
index 9879a3a..8263252 100644 (file)
@@ -44,26 +44,18 @@ For Refunds (cust_refund):
 For Payments (cust_pay):
 'CARD' (credit cards), 'CHEK' (electronic check/ACH),
 'LECB' (phone bill billing), 'BILL' (billing), 'PREP' (prepaid card),
 For Payments (cust_pay):
 'CARD' (credit cards), 'CHEK' (electronic check/ACH),
 'LECB' (phone bill billing), 'BILL' (billing), 'PREP' (prepaid card),
-'CASH' (cash), 'WEST' (Western Union), or 'MCRD' (Manual credit card)
+'CASH' (cash), 'WEST' (Western Union), 'MCRD' (Manual credit card),
+'PPAL' (PayPal)
 'COMP' (free) is depricated as a payment type in cust_pay
 
 =cut 
 
 'COMP' (free) is depricated as a payment type in cust_pay
 
 =cut 
 
-# was this supposed to do something?
-#sub payby {
-#  my($self,$payby) = @_;
-#  if ( defined($payby) ) {
-#    $self->setfield('payby', $payby);
-#  } 
-#  return $self->getfield('payby')
-#}
-
 =item payinfo
 
 Payment information (payinfo) can be one of the following types:
 
 =item payinfo
 
 Payment information (payinfo) can be one of the following types:
 
-Card Number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) or prepayment identifier (see L<FS::prepay_credit>)
+Card Number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) 
+prepayment identifier (see L<FS::prepay_credit>), PayPal transaction ID
 
 =cut
 
 
 =cut
 
@@ -267,6 +259,8 @@ sub payby_payinfo_pretty {
     'Western Union'; #. $self->payinfo;
   } elsif ( $self->payby eq 'MCRD' ) {
     'Manual credit card'; #. $self->payinfo;
     'Western Union'; #. $self->payinfo;
   } elsif ( $self->payby eq 'MCRD' ) {
     'Manual credit card'; #. $self->payinfo;
+  } elsif ( $self->payby eq 'PPAL' ) {
+    'PayPal transaction#' . $self->order_number;
   } else {
     $self->payby. ' '. $self->payinfo;
   }
   } else {
     $self->payby. ' '. $self->payinfo;
   }
index 4a7585e..e94a62c 100644 (file)
@@ -41,7 +41,7 @@ currently supported:
 
 =item gateway_namespace - Business::OnlinePayment, Business::OnlineThirdPartyPayment, or Business::BatchPayment
 
 
 =item gateway_namespace - Business::OnlinePayment, Business::OnlineThirdPartyPayment, or Business::BatchPayment
 
-=item gateway_module - Business::OnlinePayment:: module name
+=item gateway_module - Business::OnlinePayment:: (or other) module name
 
 =item gateway_username - payment gateway username
 
 
 =item gateway_username - payment gateway username
 
@@ -51,6 +51,14 @@ currently supported:
 
 =item disabled - Disabled flag, empty or 'Y'
 
 
 =item disabled - Disabled flag, empty or 'Y'
 
+=item gateway_callback_url - For ThirdPartyPayment only, set to the URL that 
+the user should be redirected to on a successful payment.  This will be sent
+as a transaction parameter (named "callback_url").
+
+=item gateway_cancel_url - For ThirdPartyPayment only, set to the URL that 
+the user should be redirected to if they cancel the transaction.  PayPal
+requires this; other gateways ignore it.
+
 =item auto_resolve_status - For BatchPayment only, set to 'approve' to 
 auto-approve unresolved payments after some number of days, 'reject' to 
 auto-decline them, or null to do nothing.
 =item auto_resolve_status - For BatchPayment only, set to 'approve' to 
 auto-approve unresolved payments after some number of days, 'reject' to 
 auto-decline them, or null to do nothing.
@@ -128,6 +136,7 @@ sub check {
     || $self->ut_textn('gateway_username')
     || $self->ut_anything('gateway_password')
     || $self->ut_textn('gateway_callback_url')  # a bit too permissive
     || $self->ut_textn('gateway_username')
     || $self->ut_anything('gateway_password')
     || $self->ut_textn('gateway_callback_url')  # a bit too permissive
+    || $self->ut_textn('gateway_cancel_url')
     || $self->ut_enum('disabled', [ '', 'Y' ] )
     || $self->ut_enum('auto_resolve_status', [ '', 'approve', 'reject' ])
     || $self->ut_numbern('auto_resolve_days')
     || $self->ut_enum('disabled', [ '', 'Y' ] )
     || $self->ut_enum('auto_resolve_status', [ '', 'approve', 'reject' ])
     || $self->ut_numbern('auto_resolve_days')
@@ -152,8 +161,8 @@ sub check {
   }
 
   # this little kludge mimics FS::CGI::popurl
   }
 
   # this little kludge mimics FS::CGI::popurl
-  $self->gateway_callback_url($self->gateway_callback_url. '/')
-    if ( $self->gateway_callback_url && $self->gateway_callback_url !~ /\/$/ );
+  #$self->gateway_callback_url($self->gateway_callback_url. '/')
+  #  if ( $self->gateway_callback_url && $self->gateway_callback_url !~ /\/$/ );
 
   $self->SUPER::check;
 }
 
   $self->SUPER::check;
 }
index b5b9eea..59ee93b 100755 (executable)
@@ -8,7 +8,7 @@ onSubmit="document.OneTrueForm.process.disabled=true">
 <INPUT TYPE="hidden" NAME="session" VALUE="<%=$session_id%>">
 <INPUT TYPE="hidden" NAME="action" VALUE="post_thirdparty_payment">
 <INPUT TYPE="hidden" NAME="payby_method" VALUE="<%= 
 <INPUT TYPE="hidden" NAME="session" VALUE="<%=$session_id%>">
 <INPUT TYPE="hidden" NAME="action" VALUE="post_thirdparty_payment">
 <INPUT TYPE="hidden" NAME="payby_method" VALUE="<%= 
-$cgi->param('payby_method') =~ /(CC|ECHECK)/;
+$cgi->param('payby_method') =~ /(CC|ECHECK|PAYPAL)/;
 $1 %>">
 <TABLE BGCOLOR="#cccccc">
 <TR>
 $1 %>">
 <TABLE BGCOLOR="#cccccc">
 <TR>
index 9ab2622..a6352e0 100644 (file)
@@ -6,18 +6,13 @@ Hello <%= $name %>!<BR><BR>
 <%= include('small_custview') %>
 
 <BR>
 <%= include('small_custview') %>
 
 <BR>
-<%= unless ( $access_pkgnum ) {
-      $OUT .= qq!Balance: <B>\$$balance</B><BR><BR>!;
-    }
-    '';
-%>
 
 <%=
   $OUT .= qq! <B><A HREF="${url}invoices">View All Invoices</A></B> &nbsp; &nbsp; !;
 %>
 
 <%= if ( $balance > 0 ) {
 
 <%=
   $OUT .= qq! <B><A HREF="${url}invoices">View All Invoices</A></B> &nbsp; &nbsp; !;
 %>
 
 <%= if ( $balance > 0 ) {
-  if (scalar(grep $_, @hide_payment_fields)) {
+  if (scalar(grep $_, @hide_payment_fields)) { # this sucks
     $OUT .= qq! <B><A HREF="${url}make_thirdparty_payment&payby_method=CC">Make a payment</A></B><BR><BR>!;
   } else {
     $OUT .= qq! <B><A HREF="${url}make_payment">Make a payment</A></B><BR>!;
     $OUT .= qq! <B><A HREF="${url}make_thirdparty_payment&payby_method=CC">Make a payment</A></B><BR><BR>!;
   } else {
     $OUT .= qq! <B><A HREF="${url}make_payment">Make a payment</A></B><BR>!;
index 4a31b12..cf719e8 100644 (file)
@@ -23,37 +23,44 @@ unless ( $access_pkgnum ) {
       url=>'customer_order_pkg', 'indent'=>2 };
 }
 
       url=>'customer_order_pkg', 'indent'=>2 };
 }
 
+my %payby_mode;
+@payby_mode{@cust_paybys} = @hide_payment_fields;
+# $payby_mode{FOO} is true if FOO is thirdparty, false if it's B::OP,
+# nonexistent if it's not supported
+
 if ( $balance > 0 ) { #XXXFIXME "enable selfservice prepay features" flag or something, eventually per-pkg or something really fancy
 
 if ( $balance > 0 ) { #XXXFIXME "enable selfservice prepay features" flag or something, eventually per-pkg or something really fancy
 
-  #XXXFIXME still a bit sloppy for multi-gateway of differing namespace
-  my $i = 0;
-  while($i < scalar(@cust_paybys)) { last if $cust_paybys[$i] =~ /^CARD/; $i++ }
-  if ( $cust_paybys[$i] && $cust_paybys[$i] =~ /^CARD/ ) {
+  if ( exists( $payby_mode{CARD} ) ) {
     push @menu, { title  => 'Recharge my account with a credit card',
     push @menu, { title  => 'Recharge my account with a credit card',
-                  url    => $hide_payment_fields[$i]
+                  url    => $payby_mode{CARD}
                               ? 'make_thirdparty_payment&payby_method=CC'
                               : 'make_payment',
                   indent => 2,
                  }
   }
 
                               ? 'make_thirdparty_payment&payby_method=CC'
                               : 'make_payment',
                   indent => 2,
                  }
   }
 
-  $i = 0;
-  while($i < scalar(@cust_paybys)) { last if $cust_paybys[$i] =~ /^CHEK/; $i++ }
-  if ( $cust_paybys[$i] && $cust_paybys[$i] =~ /^CHEK/ ) {
+  if ( exists( $payby_mode{CHEK} ) ) {
     push @menu, { title  => 'Recharge my account with a check',
     push @menu, { title  => 'Recharge my account with a check',
-                  url    => $hide_payment_fields[$i]
+                  url    => $payby_mode{CHEK}
                               ? 'make_thirdparty_payment&payby_method=ECHECK'
                               : 'make_ach_payment',
                   indent => 2,
                 }
   }
 
                               ? 'make_thirdparty_payment&payby_method=ECHECK'
                               : 'make_ach_payment',
                   indent => 2,
                 }
   }
 
-  push @menu, { title  => 'Recharge my account with a prepaid card',
-                url    => 'recharge_prepay',
-                indent => 2,
-              }
-    if grep(/^PREP/, @cust_paybys);
+  if ( exists( $payby_mode{PREP} ) ) {
+    push @menu, { title  => 'Recharge my account with a prepaid card',
+                  url    => 'recharge_prepay',
+                  indent => 2,
+                }
+  }
 
 
+  if ( exists( $payby_mode{PPAL} ) ) {
+    push @menu, { title  => 'Recharge my account with PayPal',
+                  url    => 'make_thirdparty_payment&payby_method=PAYPAL',
+                  indent => 2,
+                }
+  }
 }
 
 push @menu,
 }
 
 push @menu,
index f7fe308..40fe98a 100755 (executable)
@@ -667,12 +667,15 @@ sub make_thirdparty_payment {
 }
 
 sub post_thirdparty_payment {
 }
 
 sub post_thirdparty_payment {
-  $cgi->param('payby_method') =~ /^(CC|ECHECK)$/
+  $cgi->param('payby_method') =~ /^(CC|ECHECK|PAYPAL)$/
     or die "illegal payby method";
   my $method = $1;
   $cgi->param('amount') =~ /^(\d+(\.\d*)?)$/
     or die "illegal amount";
   my $amount = $1;
     or die "illegal payby method";
   my $method = $1;
   $cgi->param('amount') =~ /^(\d+(\.\d*)?)$/
     or die "illegal amount";
   my $amount = $1;
+  # realtime_collect() returns the result from FS::cust_main->realtime_collect
+  # which returns realtime_bop()
+  # which returns a hashref of popup_url, collectitems, and reference
   my $result = realtime_collect( 
     'session_id' => $session_id,
     'method' => $method, 
   my $result = realtime_collect( 
     'session_id' => $session_id,
     'method' => $method, 
index d9346b8..ff209d2 100755 (executable)
@@ -87,11 +87,14 @@ my $rv = capture_payment(
                      map { $_ => scalar($cgi->param($_)) } $cgi->param
                    },
            url  => $cgi->self_url,
                      map { $_ => scalar($cgi->param($_)) } $cgi->param
                    },
            url  => $cgi->self_url,
+           cancel => ($cgi->param('cancel') ? 1 : 0),
 );
 
 $error = $rv->{error};
 );
 
 $error = $rv->{error};
-  
-if ( $error eq '_decline' ) {
+
+if ( $error eq '_cancel' ) {
+  print_okay(%$rv);
+} elsif ( $error eq '_decline' ) {
   print_decline();
 } elsif ( $error ) {
   print_verify();
   print_decline();
 } elsif ( $error ) {
   print_verify();
@@ -133,8 +136,14 @@ sub print_okay {
     $success_url .= '/signup.cgi?action=success';
   }
 
     $success_url .= '/signup.cgi?action=success';
   }
 
-  print $cgi->header( '-expires' => 'now' ),
-        $success_template->fill_in( HASH => { success_url => $success_url } );
+  if ( $param{error} eq '_cancel' ) {
+    # then the payment was canceled, so don't show a message, just redirect
+    # (during signup, you really need a separate landing page for this case)
+    print $cgi->redirect($success_url);
+  } else {
+    print $cgi->header( '-expires' => 'now' ),
+          $success_template->fill_in( HASH => { success_url => $success_url } );
+  }
 }
 
 sub success_default { #html to use if you don't specify a success file
 }
 
 sub success_default { #html to use if you don't specify a success file
index 4a7cedf..41a9f3e 100644 (file)
@@ -34,6 +34,7 @@ for <SELECT NAME="cardtype" MULTIPLE>
 %  "Switch",
 %  "Solo",
 %  'ACH',
 %  "Switch",
 %  "Solo",
 %  'ACH',
+%  'PayPal',
 %) { 
 
   <OPTION VALUE="<% $cardtype %>"><% $cardtype || '(Default fallback)' %>
 %) { 
 
   <OPTION VALUE="<% $cardtype %>"><% $cardtype || '(Default fallback)' %>
index a469beb..7cfab71 100644 (file)
                                   'gateway_action'       => 'Action',
                                   'gateway_options'      => 'Options (Name/Value pairs, <BR>one element per line)',
                                   'gateway_callback_url' => 'Callback URL',
                                   'gateway_action'       => 'Action',
                                   'gateway_options'      => 'Options (Name/Value pairs, <BR>one element per line)',
                                   'gateway_callback_url' => 'Callback URL',
+                                  'gateway_cancel_url'   => 'Cancel URL',
                                 },
           )
 %>
 
 
 <SCRIPT TYPE="text/javascript">
                                 },
           )
 %>
 
 
 <SCRIPT TYPE="text/javascript">
-  var modulesForNamespace = <% encode_json(\%modules_for_namespace, {canonical=>1}) %>;
-  function changeNamespace(what) {
-    var ns = what.value;
+  var modulesForNamespace = <% $json->encode(\%modules) %>;
+  function changeNamespace() {
+    var ns = document.getElementById('gateway_namespace').value;
     var select_module = document.getElementById('gateway_module');
     select_module.options.length = 0;
     for (var x in modulesForNamespace[ns]) {
     var select_module = document.getElementById('gateway_module');
     select_module.options.length = 0;
     for (var x in modulesForNamespace[ns]) {
@@ -30,6 +31,7 @@
       select_module.add(o, null);
     }
   }
       select_module.add(o, null);
     }
   }
+  window.onload = changeNamespace;
 </SCRIPT>
 
 <%init>
 </SCRIPT>
 
 <%init>
 die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
 
 die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
 
-my %modules =  (
-  '2CheckOut'             => 'Business::OnlinePayment',
-  'AuthorizeNet'          => 'Business::OnlinePayment',
-  'BankOfAmerica'         => 'Business::OnlinePayment', #deprecated?
-  'Beanstream'            => 'Business::OnlinePayment',
-  'Capstone'              => 'Business::OnlinePayment',
-  'Cardstream'            => 'Business::OnlinePayment',
-  'CashCow'               => 'Business::OnlinePayment',
-  'CyberSource'           => 'Business::OnlinePayment',
-  'eSec'                  => 'Business::OnlinePayment',
-  'eSelectPlus'           => 'Business::OnlinePayment',
-  'eWayShared'            => 'Business::OnlineThirdPartyPayment',
-  'ElavonVirtualMerchant' => 'Business::OnlinePayment',
-  'Exact'                 => 'Business::OnlinePayment',
-  'iAuthorizer'           => 'Business::OnlinePayment',
-  'Ingotz'                => 'Business::OnlinePayment',
-  'InternetSecure'        => 'Business::OnlinePayment',
-  'Interswitchng'         => 'Business::OnlineThirdPartyPayment',
-  'IPaymentTPG'           => 'Business::OnlinePayment',
-  'IPPay'                 => 'Business::OnlinePayment',
-  'Iridium'               => 'Business::OnlinePayment',
-  'Jettis'                => 'Business::OnlinePayment',
-  'Jety'                  => 'Business::OnlinePayment',
-  'LinkPoint'             => 'Business::OnlinePayment',
-  'MerchantCommerce'      => 'Business::OnlinePayment',
-  'Network1Financial'     => 'Business::OnlinePayment',
-  'OCV'                   => 'Business::OnlinePayment',
-  'OpenECHO'              => 'Business::OnlinePayment',
-  'PayConnect'            => 'Business::OnlinePayment',
-  'PayflowPro'            => 'Business::OnlinePayment',
-  'PaymenTech'            => 'Business::OnlinePayment',
-  'PaymentsGateway'       => 'Business::OnlinePayment',
-  'PayPal'                => 'Business::OnlinePayment',
-  #'PaySystems'            => 'Business::OnlinePayment',
-  'PlugnPay'              => 'Business::OnlinePayment',
-  'PPIPayMover'           => 'Business::OnlinePayment',
-  'Protx'                 => 'Business::OnlinePayment', #now SagePay
-  'PXPost'                => 'Business::OnlinePayment',
-  'SagePay'               => 'Business::OnlinePayment',
-  'SecureHostingUPG'      => 'Business::OnlinePayment',
-  'Skipjack'              => 'Business::OnlinePayment',
-  'StGeorge'              => 'Business::OnlinePayment',
-  'SurePay'               => 'Business::OnlinePayment',
-  'TCLink'                => 'Business::OnlinePayment',
-  'TransactionCentral'    => 'Business::OnlinePayment',
-  'TransFirsteLink'       => 'Business::OnlinePayment',
-  'Vanco'                 => 'Business::OnlinePayment',
-  'viaKLIX'               => 'Business::OnlinePayment',
-  'VirtualNet'            => 'Business::OnlinePayment',
-  'WesternACH'            => 'Business::OnlinePayment',
-  'WorldPay'              => 'Business::OnlinePayment',
-
-  'KeyBank'               => 'Business::BatchPayment',
-  'Paymentech'            => 'Business::BatchPayment',
-  'TD_EFT'                => 'Business::BatchPayment',
+my $json = JSON::XS->new;
+$json->canonical(1);
+my %modules = (
+  'Business::OnlinePayment' => [
+    '2CheckOut',
+    'AuthorizeNet',
+    'BankOfAmerica', #deprecated?
+    'Beanstream',
+    'Capstone',
+    'Cardstream',
+    'CashCow',
+    'CyberSource',
+    'eSec',
+    'eSelectPlus',
+    'ElavonVirtualMerchant',
+    'Exact',
+    'iAuthorizer',
+    'Ingotz',
+    'InternetSecure',
+    'IPaymentTPG',
+    'IPPay',
+    'Iridium',
+    'Jettis',
+    'Jety',
+    'LinkPoint',
+    'MerchantCommerce',
+    'Network1Financial',
+    'OCV',
+    'OpenECHO',
+    'PayConnect',
+    'PayflowPro',
+    'PaymenTech',
+    'PaymentsGateway',
+    'PayPal',
+    #'PaySystems',
+    'PlugnPay',
+    'PPIPayMover',
+    'Protx', #now SagePay
+    'PXPost',
+    'SagePay',
+    'SecureHostingUPG',
+    'Skipjack',
+    'StGeorge',
+    'SurePay',
+    'TCLink',
+    'TransactionCentral',
+    'TransFirsteLink',
+    'Vanco',
+    'viaKLIX',
+    'VirtualNet',
+    'WesternACH',
+    'WorldPay',
+  ],
+  'Business::OnlineThirdPartyPayment' => [
+    'eWayShared',
+    'Interswitchng',
+    'PayPal',
+  ],
+  'Business::BatchPayment' => [
+    'KeyBank',
+    'Paymentech',
+    'TD_EFT',
+  ],
 );
 
 );
 
-my %modules_for_namespace;
-for (keys %modules) {
-  $modules_for_namespace{$modules{$_}} ||= [];
-  push @{ $modules_for_namespace{$modules{$_}} }, $_;
-}
-
 my @actions = (
                 'Normal Authorization',
                 'Authorization Only',
 my @actions = (
                 'Normal Authorization',
                 'Authorization Only',
@@ -125,7 +129,9 @@ my $fields = [
                {
                  field    => 'gateway_module',
                  type     => 'select',
                {
                  field    => 'gateway_module',
                  type     => 'select',
-                 options  => [ sort { lc($a) cmp lc ($b) } keys %modules ],
+                 # does it even make sense to list all modules here?
+                 options  => [ sort { lc($a) cmp lc ($b) }
+                               map { @$_ } values %modules ],
                },
                'gateway_username',
                'gateway_password',
                },
                'gateway_username',
                'gateway_password',
@@ -140,6 +146,11 @@ my $fields = [
                  size     => 40,
                },
                {
                  size     => 40,
                },
                {
+                 field    => 'gateway_cancel_url',
+                 type     => 'text',
+                 size     => 40,
+               },
+               {
                  field               => 'gateway_options',
                  type                => 'textarea',
                  rows                => '12',
                  field               => 'gateway_options',
                  type                => 'textarea',
                  rows                => '12',
index 812c988..157449e 100644 (file)
@@ -15,6 +15,7 @@ my $args_callback = sub {
   my @options = split(/\r?\n/, $cgi->param('gateway_options') );
   pop @options
     if scalar(@options) % 2 && $options[-1] =~ /^\s*$/;
   my @options = split(/\r?\n/, $cgi->param('gateway_options') );
   pop @options
     if scalar(@options) % 2 && $options[-1] =~ /^\s*$/;
+  @options = ( {} ) if !@options;
   (@options)
 };
 
   (@options)
 };