diff options
author | Ivan Kohler <ivan@freeside.biz> | 2013-04-29 12:31:20 -0700 |
---|---|---|
committer | Ivan Kohler <ivan@freeside.biz> | 2013-04-29 12:31:20 -0700 |
commit | 22336a0eeba10b4d1a4a78801f83f7cef56a1a20 (patch) | |
tree | 6d72adef0e02a6fbfbcba3969f2c24b6596d92e5 | |
parent | a0b714142b349a36eee41a5de1e511758a382ffb (diff) | |
parent | 00755aee33cc3ecdf3634bcc0f50f54814bfc400 (diff) |
Merge branch 'master' of git.freeside.biz:/home/git/freeside
26 files changed, 284 insertions, 178 deletions
diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm index 16d6c8f3a..57091c4fe 100644 --- a/FS/FS/ClientAPI/Signup.pm +++ b/FS/FS/ClientAPI/Signup.pm @@ -946,15 +946,27 @@ sub capture_payment { } 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, + }; + } } diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 0db5d86cc..db21563dc 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -2098,7 +2098,7 @@ and customer address. Include units.', '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) ], }, { @@ -2479,7 +2479,7 @@ and customer address. Include units.', '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) ], }, { @@ -2487,7 +2487,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', - '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) ], }, { diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index eb73ccbc8..4f395f285 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -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_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', diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm index 3794d3f1d..9b322093b 100644 --- a/FS/FS/agent.pm +++ b/FS/FS/agent.pm @@ -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 -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. @@ -246,13 +246,17 @@ sub payment_gateway { } #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 = diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 2a4602e19..1cf036551 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -390,7 +390,7 @@ sub insert { $payby = 'PREP' if $amount; - } elsif ( $self->payby =~ /^(CASH|WEST|MCRD)$/ ) { + } elsif ( $self->payby =~ /^(CASH|WEST|MCRD|PPAL)$/ ) { $payby = $1; $self->payby('BILL'); @@ -2021,7 +2021,8 @@ sub check { 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 ); diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index 804969b16..1caa3e5af 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -111,7 +111,7 @@ L<http://420.am/business-onlinepayment> for supported gateways. 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> @@ -317,6 +317,7 @@ my %bop_method2payby = ( 'CC' => 'CARD', 'ECHECK' => 'CHEK', 'LEC' => 'LECB', + 'PAYPAL' => 'PPAL', ); 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, + 'cancel_url' => $payment_gateway->gateway_cancel_url, 'email' => $email, %content, #after ); diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 741d440fa..be3acb9ee 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -1037,15 +1037,20 @@ sub uncancel { $dbh->rollback if $oldAutoCommit; return $svc_error; } else { + # if we've failed to insert the svc_x object, svc_Common->insert + # will have removed the cust_svc already. if not, then both records + # were inserted but we failed for some other reason (export, most + # likely). in that case, report the error and delete the records. push @svc_errors, $svc_error; - # is this necessary? svc_Common::insert already deletes the - # cust_svc if inserting svc_x fails. my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_x->svcnum }); if ( $cust_svc ) { - my $cs_error = $cust_svc->delete; - if ( $cs_error ) { + # except if export_insert failed, export_delete probably won't be + # much better + local $FS::svc_Common::noexport_hack = 1; + my $cleanup_error = $svc_x->delete; # also deletes cust_svc + if ( $cleanup_error ) { # and if THAT fails, then run away $dbh->rollback if $oldAutoCommit; - return $cs_error; + return $cleanup_error; } } } # svc_fatal diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index 165384048..cab80a871 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -863,32 +863,34 @@ sub smart_search_param { my @or = map { my $table = $_; my $search_sql = "FS::$table"->search_sql($string); - " ( svcdb = '$table' - AND 0 < ( SELECT COUNT(*) FROM $table - WHERE $table.svcnum = cust_svc.svcnum - AND $search_sql - ) - ) "; + + "SELECT $table.svcnum AS svcnum, '$table' AS svcdb ". + "FROM $table WHERE $search_sql"; } FS::part_svc->svc_tables; if ( $string =~ /^(\d+)$/ ) { - unshift @or, " ( agent_svcid IS NOT NULL AND agent_svcid = $1 ) "; + unshift @or, "SELECT cust_svc.svcnum, NULL FROM cust_svc WHERE agent_svcid = $1"; } - my @extra_sql = ' ( '. join(' OR ', @or). ' ) '; + my $addl_from = " RIGHT JOIN (\n" . join("\nUNION\n", @or) . "\n) AS svc_all ". + " ON (svc_all.svcnum = cust_svc.svcnum) "; + + my @extra_sql; push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( 'null_right' => 'View/link unlinked services' ); my $extra_sql = ' WHERE '.join(' AND ', @extra_sql); #for agentnum - my $addl_from = ' LEFT JOIN cust_pkg USING ( pkgnum )'. + $addl_from .= ' LEFT JOIN cust_pkg USING ( pkgnum )'. FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'). ' LEFT JOIN part_svc USING ( svcpart )'; ( 'table' => 'cust_svc', + 'select' => 'svc_all.svcnum AS svcnum, '. + 'COALESCE(svc_all.svcdb, part_svc.svcdb) AS svcdb', 'addl_from' => $addl_from, 'hashref' => {}, 'extra_sql' => $extra_sql, diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm index d1961a58d..e223a050f 100644 --- a/FS/FS/payby.pm +++ b/FS/FS/payby.pm @@ -208,6 +208,7 @@ sub longname { 'CARD' => 'CC', 'CHEK' => 'ECHECK', 'MCRD' => 'CC', + 'PPAL' => 'PAYPAL', ); sub payby2bop { diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm index 9879a3abd..82632526d 100644 --- a/FS/FS/payinfo_Mixin.pm +++ b/FS/FS/payinfo_Mixin.pm @@ -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), -'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 -# 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: -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 @@ -267,6 +259,8 @@ sub payby_payinfo_pretty { '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; } diff --git a/FS/FS/payment_gateway.pm b/FS/FS/payment_gateway.pm index 4a7585e24..e94a62cf4 100644 --- a/FS/FS/payment_gateway.pm +++ b/FS/FS/payment_gateway.pm @@ -41,7 +41,7 @@ currently supported: =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 @@ -51,6 +51,14 @@ currently supported: =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. @@ -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_cancel_url') || $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 - $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; } diff --git a/fs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html b/fs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html index b5b9eea1f..59ee93b00 100755 --- a/fs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html +++ b/fs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html @@ -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="<%= -$cgi->param('payby_method') =~ /(CC|ECHECK)/; +$cgi->param('payby_method') =~ /(CC|ECHECK|PAYPAL)/; $1 %>"> <TABLE BGCOLOR="#cccccc"> <TR> diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount.html b/fs_selfservice/FS-SelfService/cgi/myaccount.html index 9ab262261..a6352e07a 100644 --- a/fs_selfservice/FS-SelfService/cgi/myaccount.html +++ b/fs_selfservice/FS-SelfService/cgi/myaccount.html @@ -6,18 +6,13 @@ Hello <%= $name %>!<BR><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> !; %> <%= 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>!; diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html index 4a31b1258..cf719e849 100644 --- a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html +++ b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html @@ -23,37 +23,44 @@ unless ( $access_pkgnum ) { 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 - #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', - url => $hide_payment_fields[$i] + url => $payby_mode{CARD} ? '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', - url => $hide_payment_fields[$i] + url => $payby_mode{CHEK} ? '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, diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi index f7fe308cf..40fe98af2 100755 --- a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi +++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi @@ -667,12 +667,15 @@ sub make_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; + # 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, diff --git a/fs_selfservice/FS-SelfService/cgi/verify.cgi b/fs_selfservice/FS-SelfService/cgi/verify.cgi index d9346b897..ff209d2f9 100755 --- a/fs_selfservice/FS-SelfService/cgi/verify.cgi +++ b/fs_selfservice/FS-SelfService/cgi/verify.cgi @@ -87,11 +87,14 @@ my $rv = capture_payment( map { $_ => scalar($cgi->param($_)) } $cgi->param }, url => $cgi->self_url, + cancel => ($cgi->param('cancel') ? 1 : 0), ); $error = $rv->{error}; - -if ( $error eq '_decline' ) { + +if ( $error eq '_cancel' ) { + print_okay(%$rv); +} elsif ( $error eq '_decline' ) { print_decline(); } elsif ( $error ) { print_verify(); @@ -133,8 +136,14 @@ sub print_okay { $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 diff --git a/httemplate/edit/agent_payment_gateway.html b/httemplate/edit/agent_payment_gateway.html index 4a7cedf79..41a9f3e95 100644 --- a/httemplate/edit/agent_payment_gateway.html +++ b/httemplate/edit/agent_payment_gateway.html @@ -34,6 +34,7 @@ for <SELECT NAME="cardtype" MULTIPLE> % "Switch", % "Solo", % 'ACH', +% 'PayPal', %) { <OPTION VALUE="<% $cardtype %>"><% $cardtype || '(Default fallback)' %> diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html index a469beb7f..7cfab71d8 100644 --- a/httemplate/edit/payment_gateway.html +++ b/httemplate/edit/payment_gateway.html @@ -13,15 +13,16 @@ '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"> - 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]) { @@ -30,6 +31,7 @@ select_module.add(o, null); } } + window.onload = changeNamespace; </SCRIPT> <%init> @@ -37,69 +39,71 @@ 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', @@ -125,7 +129,9 @@ my $fields = [ { 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', @@ -140,6 +146,11 @@ my $fields = [ size => 40, }, { + field => 'gateway_cancel_url', + type => 'text', + size => 40, + }, + { field => 'gateway_options', type => 'textarea', rows => '12', diff --git a/httemplate/edit/process/payment_gateway.html b/httemplate/edit/process/payment_gateway.html index 812c988c5..157449e89 100644 --- a/httemplate/edit/process/payment_gateway.html +++ b/httemplate/edit/process/payment_gateway.html @@ -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*$/; + @options = ( {} ) if !@options; (@options) }; diff --git a/httemplate/elements/select-tiered.html b/httemplate/elements/select-tiered.html index 3ff5471ae..48469dc04 100644 --- a/httemplate/elements/select-tiered.html +++ b/httemplate/elements/select-tiered.html @@ -174,6 +174,8 @@ for( $i = 0; $i < @$tiers; $i++ ) { $children_of{$key}->{''} = $tier->{empty_label}; } } + # ensure that there's always at least one empty label + $children_of{''}->{''} = $tier->{empty_label}; } $tier->{by_key} = \%children_of; } diff --git a/httemplate/misc/cancel-unaudited.cgi b/httemplate/misc/cancel-unaudited.cgi index 4919c6632..4b3084f00 100755 --- a/httemplate/misc/cancel-unaudited.cgi +++ b/httemplate/misc/cancel-unaudited.cgi @@ -15,19 +15,32 @@ my($query) = $cgi->keywords; $query =~ /^(\d+)$/; my $svcnum = $1; -#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum}); -#die "Unknown svcnum!" unless $svc_acct; - +my $error = ''; my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum}); -die "Unknown svcnum!" unless $cust_svc; -my $cust_pkg = $cust_svc->cust_pkg; -if ( $cust_pkg ) { - errorpage( 'This account has already been audited. Cancel the '. - qq!<A HREF="${p}view/cust_main.cgi?!. $cust_pkg->custnum. - '#cust_pkg'. $cust_pkg->pkgnum. '">'. - 'package</A> instead.'); -} +if ( $cust_svc ) { + my $cust_pkg = $cust_svc->cust_pkg; + if ( $cust_pkg ) { + errorpage( 'This account has already been audited. Cancel the '. + qq!<A HREF="${p}view/cust_main.cgi?!. $cust_pkg->custnum. + '#cust_pkg'. $cust_pkg->pkgnum. '">'. + 'package</A> instead.'); #' + } -my $error = $cust_svc->cancel; + $error = $cust_svc->cancel; +} else { + # the rare obscure case: svc_x without cust_svc + my $svc_x; + foreach my $svcdb (FS::part_svc->svc_tables) { + $svc_x = qsearchs($svcdb, { 'svcnum' => $svcnum }); + last if $svc_x; + } + if ( $svc_x ) { + $error = $svc_x->return_inventory + || $svc_x->FS::Record::delete; + } else { + # the svcnum really doesn't exist + $error = "svcnum $svcnum not found"; + } +} </%init> diff --git a/httemplate/misc/delete-note.html b/httemplate/misc/delete-note.html new file mode 100644 index 000000000..436326ff1 --- /dev/null +++ b/httemplate/misc/delete-note.html @@ -0,0 +1,11 @@ +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Edit customer note'); + +my ($notenum) = $cgi->keywords; +$notenum =~ /^\d+$/ or die "bad notenum '$notenum'"; +my $note = FS::cust_main_note->by_key($notenum) + or die "notenum '$notenum' not found"; +$note->delete; +</%init> +<% $cgi->redirect($p.'view/cust_main.cgi?'.$note->custnum) %> diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html index e2a83b7de..b245d3114 100644 --- a/httemplate/search/cust_svc.html +++ b/httemplate/search/cust_svc.html @@ -96,6 +96,7 @@ if ( length( $cgi->param('search_svc') ) ) { my $extra_sql = ' WHERE '. join(' AND ', @extra_sql ); $sql_query = { + 'select' => 'svcnum', 'table' => 'cust_svc', 'addl_from' => $addl_from, 'hashref' => {}, @@ -105,8 +106,8 @@ if ( length( $cgi->param('search_svc') ) ) { } $sql_query->{'select'} = join(', ', - 'cust_svc.*', - 'part_svc.*', + $sql_query->{'select'}, + #'part_svc.*', 'cust_main.custnum', FS::UI::Web::cust_sql_fields(), ); @@ -117,14 +118,17 @@ my $count_query = "SELECT COUNT(*) FROM cust_svc ". $sql_query->{addl_from}. my $link = sub { my $cust_svc = shift; - my $url = svc_url( - 'm' => $m, - 'action' => 'view', - #'part_svc' => $cust_svc->part_svc, - 'svcdb' => $cust_svc->svcdb, #we have it from the joined search - #'svc' => $cust_svc, #redundant - 'query' => '', - ); + my $url; + if ( $cust_svc->svcpart ) { + $url = svc_url( + 'm' => $m, + 'action' => 'view', + 'svcdb' => $cust_svc->svcdb, #we have it from the joined search + 'query' => '', + ); + } else { # bizarre unlinked service case + $url = $p.'view/svc_Common.html?svcdb='.$cust_svc->svcdb.';svcnum='; + } [ $url, 'svcnum' ]; }; diff --git a/httemplate/view/cust_main/notes.html b/httemplate/view/cust_main/notes.html index 1e9f464db..2de68ff46 100755 --- a/httemplate/view/cust_main/notes.html +++ b/httemplate/view/cust_main/notes.html @@ -63,7 +63,11 @@ % % my $edit = ''; % if ($curuser->access_right('Edit customer note') ) { -% $edit = qq! <A HREF="javascript:void(0);" $clickjs>(!.emt('edit').')</A>'; +% my $delete_url = $fsurl.'misc/delete-note.html?'.$notenum; +% $edit = qq! <A HREF="javascript:void(0);" $clickjs>(!.emt('edit').')</A>'. +% qq! <A HREF="$delete_url" !. +% qq! onclick="return confirm('Delete this note?')">!. +% '('.emt('delete').')</A>'; % } % % if ( $last_classnum != $note->classnum && !$skipheader ) { diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html index d735195fe..997ac142a 100644 --- a/httemplate/view/elements/svc_Common.html +++ b/httemplate/view/elements/svc_Common.html @@ -51,8 +51,10 @@ function areyousure(href) { % } <% mt('Service #') |h %><B><% $svcnum %></B> -% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?'; +% if ( $custnum ) { +% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?'; <& /view/elements/svc_edit_link.html, 'svc' => $svc_x, 'edit_url' => $url &> +% } <BR> <% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> @@ -127,7 +129,9 @@ function areyousure(href) { % } +% if ( $cust_svc ) { <& /elements/table-tickets.html, object => $cust_svc &> +% } <% joblisting({'svcnum'=>$svcnum}, 1) %> @@ -150,7 +154,7 @@ my $fields = $opt{'fields'} my $svcnum; if ( $cgi->param('svcnum') ) { - $cgi->param('svcnum') =~ /^(\d+)$/ or die "unparsable svcnum"; + $cgi->param('svcnum') =~ /^(\d+)$/ or die "unparseable svcnum"; $svcnum = $1; } else { my($query) = $cgi->keywords; @@ -170,19 +174,29 @@ my $svc_x = qsearchs({ }) or die "Unknown svcnum $svcnum in ". $opt{'table'}. " table\n"; my $cust_svc = $svc_x->cust_svc; -my($label, $value, $svcdb) = $cust_svc->label; +my ($label, $value, $svcdb, $part_svc ); +my $labels = $opt{labels}; #not -> here -my $part_svc = $cust_svc->part_svc; +if ( $cust_svc ) { + ($label, $value, $svcdb) = $cust_svc->label; -#false laziness w/edit/svc_Common.html -#override default labels with service-definition labels if applicable -my $labels = $opt{labels}; #not -> here -foreach my $field ( keys %$labels ) { - my $col = $part_svc->part_svc_column($field); - $labels->{$field} = $col->columnlabel if $col->columnlabel !~ /^\s*$/; + $part_svc = $cust_svc->part_svc; + + #false laziness w/edit/svc_Common.html + #override default labels with service-definition labels if applicable + foreach my $field ( keys %$labels ) { + my $col = $part_svc->part_svc_column($field); + $labels->{$field} = $col->columnlabel if $col->columnlabel !~ /^\s*$/; + } +} else { + $label = "Unlinked $table"; + $value = $svc_x->label; + $svcdb = $table; + # just to satisfy callbacks + $part_svc = FS::part_svc->new({ svcpart => 0, svcdb => $table }); } -my $pkgnum = $cust_svc->pkgnum; +my $pkgnum = $cust_svc->pkgnum if $cust_svc; my($cust_pkg, $custnum); if ($pkgnum) { diff --git a/httemplate/view/svc_Common.html b/httemplate/view/svc_Common.html index 7b46dc9c9..7e300b049 100644 --- a/httemplate/view/svc_Common.html +++ b/httemplate/view/svc_Common.html @@ -7,7 +7,7 @@ # false laziness w/edit/svc_Common.html -$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb"; +$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparseable svcdb"; my $table = $1; require "FS/$table.pm"; |