#!/usr/bin/perl use strict; use warnings; use Email::Sender::Simple qw(sendmail); use Sipwise::Provisioning::Billing; my %LOCK = ( none => undef, foreign => 1, outgoing => 2, incoming => 3, global => 4, 0 => 'none', 1 => 'foreign', 2 => 'outgoing', 3 => 'incoming', 4 => 'global', ); my $conf = Sipwise::Provisioning::Config->new()->get_config(); my $o = Sipwise::Provisioning::Billing->new(); my $db = $o->{database}; # We select only those billing profile hits which exceed the fraud limit and # have an action attached to keep the number of result rows small. # However, we select all account hits and manually check them later, as they # can potentially override billing profile hits. my $a = $db->sql_get_all_arrayref(<<"!"); SELECT 'profile_limit' as type, c.id, p.fraud_interval_lock, p.fraud_interval_notify, b.cash_balance_interval, p.fraud_interval_limit FROM contracts c, billing_profiles p, contract_balances b, billing_mappings bm, products pr WHERE bm.id = (SELECT m.id FROM billing_mappings m WHERE ( m.start_date IS NULL OR m.start_date <= now()) AND ( m.end_date IS NULL OR m.end_date >= now()) AND m.contract_id = c.id ORDER BY m.start_date desc limit 1) AND p.id = bm.billing_profile_id AND pr.id = bm.product_id AND (pr.class IN ('sipaccount', 'pbxaccount') OR pr.class IS NULL) AND b.contract_id = c.id AND b.start <= now() AND b.end >= now() AND c.status = 'active' AND p.fraud_interval_limit > 0 AND (p.fraud_interval_lock OR p.fraud_interval_notify <> '') AND b.cash_balance_interval >= p.fraud_interval_limit UNION ALL SELECT 'account_limit' as type, c.id, afp.fraud_interval_lock, afp.fraud_interval_notify, b.cash_balance_interval, afp.fraud_interval_limit FROM contracts c, contract_fraud_preferences afp, contract_balances b WHERE b.contract_id = c.id AND afp.contract_id = c.id AND b.start <= now() AND b.end >= now() AND c.status = 'active' ! my $x = {}; for my $e (@{ $a }) { if(exists $x->{$e->{id}}) { if($x->{$e->{id}}->{type} eq 'profile_limit' && $e->{type} eq 'account_limit') { # if account limit hits and it has lock and/or notify, mark for action if(defined $e->{fraud_interval_limit} and int($e->{cash_balance_interval}) >= int($e->{fraud_interval_limit}) and ($e->{fraud_interval_lock} || $e->{fraud_interval_notify})) { $x->{$e->{id}} = $e; } else { # we have account fraud prefs set, but either the limit is not reached # or no actions are necessary, let it slip through, overriding # billing profile fraud settings delete $x->{$e->{id}}; } } } else { # if account or billing profile limit hits and it has lock and/or notify, # mark for action if(defined $e->{fraud_interval_limit} and int($e->{cash_balance_interval}) >= int($e->{fraud_interval_limit}) and ($e->{fraud_interval_lock} || $e->{fraud_interval_notify})) { $x->{$e->{id}} = $e; } } } for my $e (values %{ $x }) { $e->{fraud_interval_lock} and $o->lock_voip_account({id => $e->{id}, lock => $LOCK{$e->{fraud_interval_lock}}}); $e->{fraud_interval_notify} or next; my $subs = $db->sql_get_all_arrayref(<<"!", $e->{id}); SELECT s.username, d.domain, s.external_id FROM voip_subscribers s LEFT JOIN domains d ON d.id = s.domain_id WHERE s.contract_id = ? AND s.status != 'terminated' ! my $cur = sprintf('%.2f', $e->{cash_balance_interval} / 100); my $max = sprintf('%.2f', $e->{fraud_interval_limit} / 100); my ($subject, $body); if ($e->{fraud_interval_lock}) { $body = "Account ID " . $e->{id} . " has been locked due to exceeding the configured" . "\n" . "credit balance threshold ($cur >= $max ) in the " . ($e->{type} eq 'profile_limit' ? 'billing profile' : 'account settings') . "\n\n"; $subject = 'Account ID ' . $e->{id} . ' locked by fraud detection'; } else { $body = "Account ID " . $e->{id} . " is currently exceeding the configured credit balance" . "\n" . "threshold ($cur >= $max) in the " . ($e->{type} eq 'profile_limit' ? 'billing profile' : 'account settings') . ",\n" . "but has not been locked due to configuration.\n\n"; $subject = 'Account ID ' . $e->{id} . ' exceeding fraud detection limit'; } if (!$subs || !@$subs) { $body .= "There are no affected subscribers.\n"; } else { $body .= "Affected subscribers:\n"; for my $s (@$subs) { $body .= "\t$s->{username}\@$s->{domain}". ($s->{external_id} ? " (external ID '$s->{external_id}')" : '') . "\n"; } } sendmail ( Email::Simple->create( header => [ To => $e->{fraud_interval_notify}, From => $$conf{adminmail}, Subject => $subject, ], body => $body, )); } 1;