}
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,
+ };
+ }
}
'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) ],
},
{
'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) ],
},
{
'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) ],
},
{
'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',
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.
}
#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 =
$payby = 'PREP' if $amount;
- } elsif ( $self->payby =~ /^(CASH|WEST|MCRD)$/ ) {
+ } elsif ( $self->payby =~ /^(CASH|WEST|MCRD|PPAL)$/ ) {
$payby = $1;
$self->payby('BILL');
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 );
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>
'CC' => 'CARD',
'ECHECK' => 'CHEK',
'LEC' => 'LECB',
+ 'PAYPAL' => 'PPAL',
);
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
);
$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
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,
'CARD' => 'CC',
'CHEK' => 'ECHECK',
'MCRD' => 'CC',
+ 'PPAL' => 'PAYPAL',
);
sub payby2bop {
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
'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;
}
=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 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.
|| $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')
}
# 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;
}
<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>
<%= 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>!;
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,
}
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,
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();
$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
% "Switch",
% "Solo",
% 'ACH',
+% 'PayPal',
%) {
<OPTION VALUE="<% $cardtype %>"><% $cardtype || '(Default fallback)' %>
'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]) {
select_module.add(o, null);
}
}
+ window.onload = changeNamespace;
</SCRIPT>
<%init>
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',
{
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',
size => 40,
},
{
+ field => 'gateway_cancel_url',
+ type => 'text',
+ size => 40,
+ },
+ {
field => 'gateway_options',
type => 'textarea',
rows => '12',
my @options = split(/\r?\n/, $cgi->param('gateway_options') );
pop @options
if scalar(@options) % 2 && $options[-1] =~ /^\s*$/;
+ @options = ( {} ) if !@options;
(@options)
};
$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;
}
$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>
--- /dev/null
+<%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) %>
my $extra_sql = ' WHERE '. join(' AND ', @extra_sql );
$sql_query = {
+ 'select' => 'svcnum',
'table' => 'cust_svc',
'addl_from' => $addl_from,
'hashref' => {},
}
$sql_query->{'select'} = join(', ',
- 'cust_svc.*',
- 'part_svc.*',
+ $sql_query->{'select'},
+ #'part_svc.*',
'cust_main.custnum',
FS::UI::Web::cust_sql_fields(),
);
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' ];
};
%
% 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 ) {
% }
<% 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) %>
% }
+% if ( $cust_svc ) {
<& /elements/table-tickets.html, object => $cust_svc &>
+% }
<% joblisting({'svcnum'=>$svcnum}, 1) %>
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;
}) 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) {
# 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";