12 use FS::UID qw(adminsuidsetup);
13 use FS::Record qw(qsearch);
16 use vars qw($smtpmachine %agent_failure_body);
19 $FS::alerter::_template::first = "";
20 $FS::alerter::_template::last = "";
21 $FS::alerter::_template::company = "";
22 $FS::alerter::_template::payby = "";
23 $FS::alerter::_template::expdate = "";
25 # Set the mail program and other variables
26 my $mail_sender = "billing\@mydomain.tld"; # or invoice_from if available
27 my $failure_recipient = "postmaster"; # or invoice_from if available
28 my $warning_time = 30 * 24 * 60 * 60;
29 my $urgent_time = 15 * 24 * 60 * 60;
30 my $panic_time = 5 * 24 * 60 * 60;
31 my $window_time = 24 * 60 * 60;
33 &untaint_argv; #what it sounds like (eww)
35 #we're at now now (and later).
38 # Get the current month
39 my ($sec,$min,$hour,$mday,$mon,$year) =
40 (localtime($_date) )[0,1,2,3,4,5];
43 # Login to the database
44 my $user = shift or die &usage;
47 # Get the needed configuration files
48 my $conf = new FS::Conf;
49 $smtpmachine = $conf->config('smtpmachine');
51 my(@customers)=qsearch('cust_main',{});
52 if (scalar(@customers) == 0)
57 # Now I can start looping
58 foreach my $customer (@customers)
60 my $paydate = $customer->getfield('paydate');
61 next if $paydate =~ /^\s*$/; #skip empty expiration dates
63 my $custnum = $customer->getfield('custnum');
64 my $first = $customer->getfield('first');
65 my $last = $customer->getfield('last');
66 my $company = $customer->getfield('company');
67 my $payby = $customer->getfield('payby');
68 my $payinfo = $customer->getfield('payinfo');
69 my $daytime = $customer->getfield('daytime');
70 my $night = $customer->getfield('night');
72 my ($payyear,$paymonth,$payday) = split (/-/,$paydate);
74 my $expire_time = timelocal(0,0,0,$payday,--$paymonth,$payyear);
76 #credit cards expire at the end of the month/year of their exp date
77 if ($payby eq 'CARD' || $payby eq 'DCRD') {
78 ($paymonth < 11) ? $paymonth++ : ($paymonth=0, $payyear++);
79 $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
83 if (($expire_time < $_date + $warning_time &&
84 $expire_time > $_date + $warning_time - $window_time) ||
85 ($expire_time < $_date + $urgent_time &&
86 $expire_time > $_date + $urgent_time - $window_time) ||
87 ($expire_time < $_date + $panic_time &&
88 $expire_time > $_date + $panic_time - $window_time)) {
90 # Prepare for sending email, now inside the customer loop so i can be agent
93 my $agentnum = $customer->agentnum;
95 $mail_sender = $conf->config('invoice_from', $agentnum )
96 if $conf->exists('invoice_from', $agentnum);
97 $failure_recipient = $conf->config('invoice_from', $agentnum)
98 if $conf->exists('invoice_from', $agentnum);
100 $ENV{MAILADDRESS} = $mail_sender;
102 my @alerter_template = $conf->config('alerter_template', $agentnum)
103 or die "cannot load config file alerter_template";
105 my $alerter = new Text::Template TYPE => 'ARRAY',
106 SOURCE => [ map "$_\n", @alerter_template ]
107 or die "can't create new Text::Template object: $Text::Template::ERROR";
109 $alerter->compile() or die "can't compile template: $Text::Template::ERROR";
111 my @packages = $customer->ncancelled_pkgs;
112 if (scalar(@packages) != 0) {
113 my @invoicing_list = $customer->invoicing_list;
114 if ( grep { $_ ne 'POST' } @invoicing_list ) {
115 my $header = new Mail::Header ( [
116 "From: $mail_sender",
117 "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
118 "Sender: $mail_sender",
119 "Reply-To: $mail_sender",
120 "Date: ". time2str("%a, %d %b %Y %X %z", time),
121 "Subject: Billing Arrangement Expiration",
123 $FS::alerter::_template::first = $first;
124 $FS::alerter::_template::last = $last;
125 $FS::alerter::_template::company = $company;
126 if ($payby eq 'CARD' || $payby eq 'DCRD') {
127 $FS::alerter::_template::payby = "credit card (" .
128 substr($payinfo, 0, 2) . "xxxxxxxxxx" .
129 substr($payinfo, -4) . ")";
130 }elsif ($payby eq 'COMP') {
131 $FS::alerter::_template::payby = "complimentary account";
133 $FS::alerter::_template::payby = "current method";
135 $FS::alerter::_template::expdate = $expire_time;
137 $FS::alerter::_template::company_name =
138 $conf->config('company_name', $agentnum);
139 $FS::alerter::_template::company_address =
140 join("\n", $conf->config('company_address', $agentnum) ). "\n";
142 my $message = new Mail::Internet (
144 'Body' => [ $alerter->fill_in( PACKAGE => 'FS::alerter::_template' ) ],
147 $message->smtpsend( Host => $smtpmachine )
148 or $message->smtpsend( Host => $smtpmachine, Debug => 1 )
149 or die "Can't send expiration email: $!";
151 } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
152 push @{$agent_failure_body{$customer->agentnum}},
153 sprintf(qq{%5d %-32.32s %4s %10s %12s %12s},
155 $first . " " . $last . " " . $company,
166 # Now I need to send failure EMAIL
168 foreach my $agentnum ( keys %agent_failure_body ) {
170 $mail_sender = $conf->config('invoice_from', $agentnum )
171 if $conf->exists('invoice_from', $agentnum);
172 $failure_recipient = $conf->config('invoice_from', $agentnum)
173 if $conf->exists('invoice_from', $agentnum);
175 $ENV{MAILADDRESS} = $mail_sender;
176 my $header = new Mail::Header ( [
177 "From: Account Processor",
178 "To: $failure_recipient",
179 "Sender: $mail_sender",
180 "Reply-To: $mail_sender",
181 "Subject: Unnotified Billing Arrangement Expirations",
184 my $message = new Mail::Internet (
186 'Body' => [ @{$agent_failure_body{$agentnum}} ],
189 $message->smtpsend( Host => $smtpmachine )
190 or $message->smtpsend( Host => $smtpmachine, Debug => 1 )
191 or die "can't send alerter failure email to $failure_recipient".
192 " via server $smtpmachine with SMTP: $!";
197 foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
198 $ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal argument \"$ARGV[$_]\"";
204 die "Usage:\n\n freeside-expiration-alerter user\n";
209 freeside-expiration-alerter - Emails notifications of credit card expirations.
213 freeside-expiration-alerter user
217 Emails customers notice that their credit card or other billing arrangement
218 is about to expire. Usually run as a cron job.
220 user: From the mapsecrets file - see config.html from the base documentation
224 Yes..... Use at your own risk. No guarantees or warrantees of any
225 kind apply to this program. Parts of this program are hacked from
226 other GNU licensed software created mainly by Ivan Kohler.
228 This is released under the GNU Public License. See www.gnu.org
229 for more information regarding this license.
233 L<FS::cust_main>, config.html from the base documentation
237 Jeff Finucane <jeff@cmh.net>